نوع داده slice | گولنگ به زبان ساده

تاریخ انتشار:
نوع داده slice | گولنگ به زبان ساده
در این قسمت با نوع داده slice آشنا میشیم , نحوه استفاده و همچنین دستورات مرتبط باهاش رو یاد بررسی میکنیم.

قبل از آشنایی با slice لازمه که یک آشنایی اولیه با آرایه ها داشته باشید. برای تعریف یک آرایه باید طول و نوع آیتم های آرایه را مشخص کرد:

var x [5]int

x[0] = 10
x[1] = 7
x[2] = 35

fmt.Println(x) // [10 7 35 0]

مقادیر درون آرایه به صورت متوالی درون حافظه رم قرار میگیرند

شکل قرارگیری آیتم های آرایه درون حافظه

به چند دلیل آرایه ها به صورت متداول مورد استفاده قرار نمیگیرند:

  • طول آرایه بعد از اینکه آن را تعریف کنیم تغییر ناپذیر خواهد بود.
  • طول آرایه بخشی از دیتا تایپ آرایه است برای مثال آرایه ی [4]int را نمیتوان به متغیری که نوع آن [5]int نسبت داد.

برای تعریف اسلایس (slice decleration) از یک جفت کروشه [ ] به همراه دیتا تایپ مورد نظر استفاده میکنیم:

package main

import "fmt"

func main() {	
	// اسلایس عددی
	var y []int
	
	// آرایه عددی با طول ۳
	var x [3]int

	fmt.Println(x) // [0 0 0]
	fmt.Println(y) // []
}

در هنگام تعریف اسلایس همانند آرایه عمل میکنیم با این تفاوت که درون جفت کروشه [ ] را خالی میگذاریم.

در Go, هر نوع داده‌ای یک مقدار پیش‌فرض به نام zero value (مقدار صفر) دارد که وقتی متغیری بدون مقداردهی اولیه تعریف می‌شود، به آن اختصاص داده می‌شود. این ویژگی باعث می‌شود که متغیرها در گولنگ همیشه مقدار معتبری داشته باشند و از خطاهای مرتبط با مقداردهی اولیه جلوگیری شود. مقدار صفر یک اسلایس (zero value) برابر با nil است. با استفاده از دستور make میتوان به اسلایس حافظه تخصیص داد.

package main

import "fmt"

func main() {
	var y []int

	y = make([]int, 3)

	fmt.Println(y) // [0 0 0]
	
	y[0] = 2
	y[1] = 4
	y[2] = 6
	
	fmt.Println(y) // [2 4 6]
}

روش دیگر برای ایجاد اسلایس استفاده از := و تابع make است:

package main

import "fmt"

func main() {
	y := make([]int, 3)

	fmt.Println(y) // [0 0 0]

	y[0] = 2
	y[1] = 4
	y[2] = 6

	fmt.Println(y) // [2 4 6]
}

در صورتی که پیش از ایجاد اسلایس, مقادیری که قرار است درون اسلایس قرار بگیرند را میدانید, می توانید از لیترال اسلایس (slice literal) استفاده کنید:

package main

import "fmt"

func main() {
	y := []int{2, 4, 6}
	fmt.Println(y) // [2 4 6]
}

پس تا اینجا یاد گرفتیم ۳ روش برای ایجاد یک اسلایس وجود دارد:

  • تعریف اسلایس و سپس تخصیص حافظه با استفاده از دستور make
  • ایجاد اسلایس با استفاده از := و دستور make
  • استفاده از لیترال اسلایس

ساختار اسلایس در حافظه

اسلایس یک دیتا استراکچر است که از سه بخش تشکیل شده است

  • یک اشاره گر (pointer) به یک آرایه
  • یک عدد صحیح که طول اسلایس را نشان میدهد (len)
  • یک عدد صحیح که ظرفیت اسلایس را نشان میدهد (cap)

هنگامی که یک اسلایس را تعریف میکنیم (declare) مقدار صفر آرایه (zero value) برابر با nil است. این بدین معناست که اشاره گر در این اسلایس به چیزی اشاره نمیکند و مقادیر len و cap برابر با صفر هستند.

slice declaration in golang
تعریف اسلایس

 هنگامی که از تابع make استفاده میکنیم, این تابع ابتدا یک اسلایس ایجاد میکند, سپس یک آرایه با اندازه (cap) داده شده ایجاد میکند و اشاره گر اسلایس را طوری تنظیم میکند که به اولین عنصر آن آرایه اشاره کند, سپس مقادیر len و cap را تنظیم میکند و در اخر اسلایس ایجاد شده را به عنوان نتیجه برمیگرداند

make a slice with equal len and cap in Golang
مقداردهی اسلایس با استفاده از تابع make با len و cap برابر

تابع make سه پارامتر به عنوان ورودی قبول میکند. دو پارامتر اول اجباری و پارامتر سوم آپشنال (دلخواه یا optional) است. پارامتر اول نمایانگر نوع دیتا, پارامتر دوم len و پارامتر سوم cap را مشخص میکند.

initial an slice using make function with unequal len and cap
مقداردهی اسلایس با استفاده از تابع make با len و cap نابرابر

طول و ظرفیت یک اسلایس را به ترتیب با توابع len و cap میتوان تشخیص داد:

package main

import "fmt"

func main() {
	var emails []string
	
	fmt.Println(len(emails), cap(emails)) // 0 0
	
	emails = make([]string, 10)
	
	fmt.Println(len(emails), cap(emails)) // 10 10
	
	emails = make([]string, 2, 5)
	
	fmt.Println(len(emails), cap(emails)) // 2 5
}

منظور از cap و len چیست؟

اگر طول (len) اسلایس برابر با ۵ باشد یعنی به اندیس های ۰ تا ۴ دسترسی خواهیم داشت (از ۰ تا ۴ در مجموع ۵ مقدار قرار خواهد گرفت):

package main
import "fmt"
func main() {
	emails := make([]float64, 2, 5)
	
	fmt.Println(len(emails), cap(emails)) // 2 5
	fmt.Println(emails)                   // 0 0
	
	emails[0] = 2
	emails[1] = 4
	
	fmt.Println(emails) // 2 4
}

در صورتی که بخواهیم عناصر خارج از محدوده len را بخوانیم یا مقدار دهی کنیم, با خطا روبرو خواهیم شد:

package main

import "fmt"

func main() {
	emails := make([]float64, 2, 5)
	
	fmt.Println(len(emails), cap(emails)) // 2 5
	fmt.Println(emails)                   // 0 0
	
	emails[0] = 2
	emails[1] = 4
	emails[2] = 5 // panic: runtime error: index out of range [2] with length 2
} 

برای افزودن یک یا چند مقدار به انتهای اسلایس میتوان از تابع append استفاده کرد.

package main

import "fmt"

func main() {
	numbers := make([]float64, 2, 5)
	
	fmt.Println(len(numbers), cap(numbers)) // 2 5
	fmt.Println(numbers)                   // 0 0
	
	numbers[0] = 2
	numbers[1] = 4
	
	numbers = append(numbers, 6, 8)
	
	fmt.Println(numbers)                                   // 2 4 6 8
	fmt.Printf("len=%d cap=%d", len(numbers), cap(numbers)) // len=4 cap=5
}

تابع append مقادیر داده شده را به اِنتهای آرایه اضافه کرده و طول آرایه را به اندازه تعدادِ مقادیر افزایش میدهد. در صورتی که طول اسلایس (len) بزرگتر از ظرفیت (cap) آن شود, آرایه ای که اشاره گرِ اسلایس به آن اشاره میکند ظرفیت ذخیره کردن مقادیر جدید را نخواهد داشت و تابع append در این مواقع به صورت زیر عمل میکند:

  1. یک آرایه جدید با ظرفیت دوبرابر آرایه قبلی ایجاد میکند.
  2. مقادیر آرایه قبلی را در آرایه جدید کپی میکند.
  3. اشاره گر اسلایس را طوری تنظیم میکند که به آرایه جدید اشاره کند.
  4. مقادیر len و cap را در اسلایس تنظیم میکند.

کپی کردن مقادیر

برای کپی کردن مقادیر از یک اسلایس به اسلایس دیگر میتوان از تابع copy استفاده کرد.

package main

import "log"

func main() {
	x := []string{"John", "Michael", "Jane", "Kyle", "Sara"}
	y := make([]string, 3)
	
	number := copy(y, x)
	
	log.Println(y)      // [John Michael Jane]
	log.Println(number) // 3
}

تابع copy به اندازه طول پارامتر اول, مقادیر را از پارامتر دوم به درون پارامتر اول کپی میکند و یک عدد که برابر با تعداد آیتمِ کپی شده است را برمیگرداند.

اسلایس کردن

شما میتوانید یک آرایه یا اسلایس را دوباره به قسمت های کوچکتر اسلایس کنید, برای اینکار می‌توانید از دستور [start:end] استفاده کنید. اسلایس ایجاد شده شامل تمامی آیتم ها از ایندکس start تا end خواهد بود اما خود end را شامل نمی شود.

package main

import "log"

func main() {
	x := [...]int{1, 1, 2, 3, 5, 8}
	y := x[0:3]
	
	log.Println(y)              // [1 1 2]
	log.Println(len(y), cap(y)) // 3 5

	y[0] = 100
	log.Println(x[0], y[0]) // 100 100
}

با اسلایس کردن یک آرایه یا اسلایس, پوینترِ اسلایسِ جدید به آرایه قبلی یا جایی که پونترِ اسلایس اصلی اشاره میکند اشاره خواهد کرد. بدین ترتیب اگر مقداری را در اسلایسِ جدید ایجاد شده تغییر دهیم در متغیر اصلی هم تغییر خواهد کرد.

initial an slice using make function with unequal len and cap
با اسلایس کردن یک آرایه, اسلایس ایجاد شده دارای پونتری خواهد بود که به آن آرایه اشاره میکند.
  • طول یا len اسلایس جدید ایجاد شده برابر با تفاضلِ اندیسِ شروع و اندیسِ پایان خواهد بود.
  • ظرفیت یا cap اسلایس جدید برابر با تفاضلِ اندیسِ شروع و ظرفیتِ آرایه یا اسلایس اصلی است.

میتوان اندیس شروع و پایان را مشخص نکرد. در این صورت اندیس شروع برابر با 0 و اندیس پایان برابر با طول آرایه یا اسلایس اصلی در نظر گرفته می‌شود.

package main

import "log"

func main() {
	a := []int{1, 1, 2, 3, 5, 8}
	log.Println(len(a), cap(a)) // 6 6

	b := a[:]
	log.Println(b)              // [1 1 2 3 5 8]
	log.Println(len(b), cap(b)) // 6 6

	c := a[3:]
	log.Println(c)              // [3 5 8]
	log.Println(len(c), cap(c)) // 3 3

	d := b[:2]
	log.Println(d)              // 1 1
	log.Println(len(d), cap(d)) // 2 6

	e := a[5:6]
	log.Println(e)              // [8]
	log.Println(len(e), cap(e)) // 1 1
}

قسمت قبلی: نوع داده متنی (string) | گولنگ به زبان ساده

قسمت بعدی: حلقه ها | گولنگ به زبان ساده