1.8 آرایه و slice

1.8 آرایه و slice

آرایه و slice دو نوع تایپ با یک وجه اشتراک در زبان گو می باشند ولی این دو نوع تایپ یکسری تفاوت هایی نیز با هم دارند که در ادامه بهشون مپردازیم.

1.8.1 تعریف آرایه #

آرایه یکی از عمومی ترین تایپ ها در زبان های برنامه نویسی آرایه هاهستند که برای نگه داری گروهی مقادیر از یک نوع تایپ استفاده می شود. برای دسترسی به هرکدام از مقادیر درون آرایه باید از اندیس استفاده کرد. معمولا برای تعریف آرایه شما تعداد مشخص و ثابتی را برای تعداد مقادیر مشخص میکنید. یعنی این آرایه قرار است چندتا مقدار نگه داری کند.

array

در مثال زیر یک نمونه کد در خصوص چگونگی تعریف آرایه قرار داده ایم :

1package main
2
3import "fmt"
4
5func main() {
6	arrayInts := [5]int{1, 25, 12354, 654, 32}
7	fmt.Println(arrayInts)
8}
1$ go run array.go
2[1 25 12354 654 32]
  1. یک متغیر کوتاه از نوع آرایه با نام arrayInts تعریف کردیم.
  2. ظرفیت آرایه را با عدد ۵ تعیین کردیم (یعنی این آرایه فقط ۵ تا مقدار بیشتر نگه داری نمی کند)
  3. سپس تایپ آرایه را از نوع int مشخص کردیم.
  4. در نهایت در همانجا آرایه را مقدار دهی کردیم. در زبان گو مقدار دهی با باز کردن {} به انگلیسی curly bracket انجام میشود.

1.8.2 مفهوم اندازه و ظرفیت (size, capacity) #

در آرایه ما ۲ تا مفهوم داریم اندازه و ظرفیت که از عنوان این مفهوم مشخص است آرایه دارای یک اندازه و ظرفیت مشخصی می باشد و اگر شما بیشتر از ظرفیت و اندازه تعیین شده مقدار دهی کنید با خطا مواجه خواهید شد.

در آرایه ظرفیت به نسبت اندازه تعیین می شود.
1package main
2
3import "fmt"
4
5func main() {
6	arrayString := [3]string{"a", "b", "c", "d"}
7	fmt.Println(arrayString)
8}
1$ go run array.go
2./prog.go:6:42: index 3 is out of bounds (>= 3)

در کد فوق ما یک آرایه با اندازه ۳ تعریف کردیم و ۴ تا مقدار داخلش قرار دادیم و پس از اجرا, با خطای تعداد مقادیر بیشتر از اندازه و ظرفیت می باشد مواجه شدیم.

1.8.2.1 تابع len و cap #

برای آرایه و slice ما ۲ تا تابع داریم که می توانیم اندازه و ظرفیت یک آرایه یا slice را بگیریم.

  • تابع len یکی از توابع بسیار کاربردی و پراستفاده هنگام کار با آرایه یا slice می باشد که می توانید اندازه آرایه یا slice را بگیرید.
  • تابع cap ظرفیت آرایه و slice را نمایش می دهد.
1package main
2
3import "fmt"
4
5func main() {
6	arrayString := [3]string{"a", "b", "c"}
7	fmt.Printf("array %v, len %d, cap %d", arrayString, len(arrayString), cap(arrayString))
8}
1$ go run main.go
2array [a b c], len 3, cap 3

1.8.3 تعریف آرایه و مقدارهی #

در مثال زیر ما یک آرایه با مقدار 5 تعریف کردیم و قصد داریم در ادامه کد, آرایه رو مقداردهی کنیم.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	nums := [5]int{}
 7	fmt.Printf("array nums values %v, len %d, cap %d", nums, len(nums), cap(nums))
 8
 9	nums[0] = 1
10	nums[1] = 2
11	nums[2] = 10
12	nums[4] = 999
13	
14	fmt.Println("")
15	fmt.Printf("array nums values %v, len %d, cap %d", nums, len(nums), cap(nums))
16}
1$ go run main.go
2array nums values [0 0 0 0 0], len 5, cap 5
3array nums values [1 2 10 0 999], len 5, cap 5

array

  1. در کد فوق در ابتدا ما یک آرایه بدون مقدار تعریف کردیم.
  2. سپس با استفاده از اندیس مقدار را در خانه مشخص قرار دادیم.

1.8.3.1 تعریف آرایه با اندازه تعیین شده توسط کامپایلر (شما اندازه رو بهش نمیدین.) #

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

1package main
2
3import "fmt"
4
5func main() {
6	nums := [...]int{1, 25, 45, 8797, 78, 879, 541, 11}
7	fmt.Printf("array nums values %v, len %d, cap %d", nums, len(nums), cap(nums))
8}
1$ go run main.go
2array nums values [1 25 45 8797 78 879 541 11], len 8, cap 8
توجه کنید زمانیکه ... (Ellipsis) برای تعریف آرایه استفاده می کنید فقط در همان لحظه تعریف می توانید آرایه رو مقدار دهی کنید.

1.8.3.2 تعریف آرایه دوبعدی یا چندبعدی #

در زبان گو همانند سایر زبان ها می توانید آرایه دوبعدی یا چند بعدی تعریف کنید. این نوع آرایه ها برای پیاده سازی ماتریس یا یکسری سناریوهای توسعه کاربردی می باشند.

1package main
2
3import "fmt"
4
5func main() {
6	nums := [2][2][2]int{{{1, 2}, {2, 3}}, {{4, 5}, {6, 7}}}
7	fmt.Printf("array nums values %v, len %d, cap %d", nums, len(nums), cap(nums))
8}
1$ go run main.go
2array nums values [[[1 2] [2 3]] [[4 5] [6 7]]], len 2, cap 2

array

1.8.3.3 مقایسه آرایه ها #

در کد زیر ما یک نمونه از مقایسه آرایه ها را قرار داده ایم که این مقایسه براساس تایپ, اندازه و مقادیر در نظر گرفته شده است.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	nums := [2]int{1, 2}
 7	nums2 := [2]int{1, 3}
 8	nums3 := [2]int{1, 2}
 9	nums4 := [3]int{1, 2, 3}
10	chars := [2]string{"a", "b"}
11
12	fmt.Println(nums == nums2) // false
13	fmt.Println(nums == nums3) // true
14	fmt.Println(nums == nums4) // error: invalid operation: nums == nums4 (mismatched types [2]int and [3]int)
15	fmt.Println(nums == chars) // error: invalid operation: nums == chars (mismatched types [2]int and [2]string)
16}

1.8.4 برش (slice) #

همانطور که می دانید آرایه یکی از مهم ترین عناوین در زبان های برنامه نویسی می باشد. اما در زبان گو slice نسبت به آرایه بسیار پراستفاده و کاربردی تر است. اگر بخواهیم خیلی ساده slice را توصیف کنیم در واقع “یک slice به عنوان یک بخش بهم پیوسته از یک آرایه تعریف می شود که شما می توانید المنت هایش را در حال اجرا افزایش یا کاهش دهید بدون آنکه ظرفیت و اندازه آن را مشخص کنید.”

اما این سوال پیش می آید علت اینکه slice به نسبت آرایه کاربرد بیشتری دارد چیست؟ آرایه دارای برخی از محدودیت ها علی الخصوص, اندازه ثابت می باشد اما در slice شما این محدودیت ها را نخواهید داشت و خیلی ساده می توانید المنت ها را افزایش, حذف و حتی کپی کنید.

در زبان گو slice ها یک پارچگی آرایه را حفظ می کنند و کار با آرایه خیلی ساده و آسان تر خواهد شد.

slice

1.8.4.1 تعریف یک slice با اندازه مشخص #

شما می توانید با استفاده از تابع make یک slice با اندازه مشخص تعریف کنید.

slice

1slice := make([]int, 5)
2
3fmt.Println(len(slice)) // Print 5
4
5fmt.Println(cap(slice)) // Print 5

1.8.4.2 تعریف یک slice با اندازه و ظرفیت مشخص #

شما می توانید با استفاده از تابع make یک slice با ظرفیت و اندازه مشخصی تعریف کنید.

slice

1slice := make([]int, 3, 5)
2
3fmt.Println(len(slice)) // Print 3
4
5fmt.Println(cap(slice)) // Print 5

توجه کنید مقدار ظرفیت نباید کمتر از مقدار اندازه باشد.

1package main
2
3import "fmt"
4
5func main() {
6	test := make([]int, 5, 4)
7	fmt.Println(test)
8}
1$ go run main.go
2./main.go:6:22: invalid argument: length and capacity swapped

1.8.4.3 تعریف یک slice با متغیر کوتاه short variable declaration #

شما خیلی ساده می توانید یک slice را توسط متغیر کوتاه ایجاد کنید.

 1slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
 2
 3fmt.Println(len(slice)) //Print 5
 4
 5fmt.Println(cap(slice)) //Print 5
 6
 7intSlice:= []int{10, 20, 30}
 8
 9fmt.Println(len(intSlice)) //Print 3
10
11fmt.Println(cap(intSlice)) //Print 3

1.8.4.4 تعریف یک slice با موقعیت های شاخص #

شما می توانید یک slice را با موقعیت های شاخص ایجاد کنید که n تا المنت با مقدار پیش فرض ایجاد می کند و در آخر x را به آخر slice اضافه می کند. (در مثال زیر ۹۹ تا المنت با مقدار 0 و در اخر یک المنت با مقدار 88) درست میکند.

1package main
2
3import "fmt"
4
5func main() {
6	test := []int{99: 88}
7	fmt.Println(len(test), cap(test))
8}

slice

1.8.4.5 تعریف یک slice خالی #

شما می توانید خیلی ساده یک slice خالی ایجاد کنید.

 1sliceOne := make([]int, 0)
 2
 3sliceTwo := []int{}
 4
 5fmt.Println(sliceOne == nil) // print false
 6
 7fmt.Println(len(sliceOne)) // print 0
 8
 9fmt.Println(cap(sliceOne)) // print 0
10
11fmt.Println(sliceTwo == nil) // print false
12
13fmt.Println(len(sliceTwo)) // print 0
14
15fmt.Println(cap(sliceTwo)) // print 0

slice

1.8.5 مقدار دهی مجدد یکی از المنت های slice یا آرایه #

شما خیلی راحت می توانید مقدار یکی از المنت های slice یا آرایه را مقدار دهی کنید.

1slice := []int{10, 20, 30, 40}
2
3fmt.Println(slice) //print [10 20 30 40]
4
5slice[1] = 25 
6
7fmt.Println(slice) // print [10 25 30 40]

slice

1.8.6 ایجاد یک slice جدید براساس یک slice از پیش تعریف شده #

شما می توانید یک slice جدید را براساس یک slice از پیش تعریف شده ایجاد کنید.

 1x := []int{10, 20, 30, 40, 50}
 2
 3fmt.Println(x) // Print [10 20 30 40 50]
 4
 5fmt.Println(len(x)) // Print 5
 6
 7fmt.Println(cap(x)) // Print 5
 8
 9y := x[1:3]
10
11fmt.Println(y) //Print [20 30]
12
13fmt.Println(len(y)) //Print 2
14
15fmt.Println(cap(y)) //Print 4

slice

  • ما یک متغیر با نام x با ۵ تا المنت مقدار دهی شده تعریف کردیم.
  • سپس یک متغیر کوتاه با نام y تعریف کردیم که متغیر x را داخلش قرار دادیم.
  • سپس به متغیر x گفتیم از اندیس ۱ تا ۳ را به y اختصاص بدهد.

توجه کنید اتفاقی که مثال بالا رخ داد این بود که ما, اندازه و ظرفیت جدیدی برای متغیر y تعیین کردیم.

Len: 3 - 1 = 2 Cap: 5 - 1 = 4

1.8.7 خطای index out of range در slice #

یک slice فقط با توجه به اندازه و اندیس هاش امکان دسترسی و مقدار دهی مجدد المنت هایش را میدهد, اما اگر شما بخاهید خارج از اندازه تعیین شده جهت مقداری دهی و یا دسترسی به slice اقدام کنید با خطای index out of range مواجه خواهید شد.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	slice := []int{10, 20, 30, 40, 50}
 7	newSlice := slice[1:3]
 8	newSlice[3] = 45
 9	fmt.Println(newSlice)
10}
1$ go run main.go
2panic: runtime error: index out of range [3] with length 2
3
4goroutine 1 [running]:
5main.main()
6	/tmp/sandbox548843089/prog.go:8 +0x5b

1.8.8 افرودن (append) المنت های یک slice #

شما خیلی ساده می توانید با استفاده از تابع append به المنت های یک slice بیفزایید .

 1slice := []int{10, 20, 30, 40, 50}
 2
 3newSlice := slice[1:3]
 4
 5fmt.Println(len(newSlice)) // Print 2
 6
 7fmt.Println(cap(newSlice)) // Print 4
 8
 9newSlice = append(newSlice, 60)
10
11fmt.Println(len(newSlice)) // Print 3
12
13fmt.Println(cap(newSlice)) // Print 4

slice

در کد زیر اتفاقی که صورت گرفته است این است که اگر شما ... Ellipsis را بعد از کلمه slice بزارید یعنی دارید میگید تمامی المنت های داخل slice به newSlice اضافه شود.

1slice := []int{10, 20, 30, 40, 50}
2
3newSlice := append(newSlice, slice...)

1.8.9 نحوه حذف یک المنت در slice #

برای حذف یک المنت در slice باید بصورت تکنیکی اینکار را انجام دهید چون زبان گو یک روش built-in برای اینکار ندارد.

slice

  • در این روش شما باید در ابتدا آخرین المنت را به المنت مورد نظر با استفاده از اندیس کپی کنید.
  • سپس آخرین المنت را از slice حذف کنید.
 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	slice := []int{10, 20, 30, 40, 50}
 7	slice[1] = slice[len(slice)-1]
 8	slice = slice[:len(slice)-1]
 9	fmt.Println(slice)
10}
  • یک روش دیگر برای حذف یک المنت از slice استفاده از تابع append است.
  • به مثال زیر توجه کنید.
 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	slice := []int{1, 2, 3, 4, 5}
 7	index := 2 // ایندکس المنتی که میخاییم حذفش کنیم
 8	slice = append(slice[:index], slice[index+1:]...)
 9	fmt.Println(slice) // خروجی: [1 2 4 5]
10}

1.8.10 تابع copy در slice #

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

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	src := []int{1, 2, 3, 4, 5}
 7	dst := make([]int, 5)
 8	numberOfElementsCopied := copy(dst, src)
 9	fmt.Println(numberOfElementsCopied, dst)
10}
1$ go run main.go
25 [1 2 3 4 5]

1.8.11 نحوه مرتب کردن (sort) یک slice #

برای مرتب کردن یک slice می توانید از توابع کتابخانه sort در زبان گو استفاده کنید.

  • sort.Ints
  • sort.Float64s
  • sort.Strings
 1package main
 2
 3import (
 4	"fmt"
 5	"sort"
 6)
 7
 8func main() {
 9	s := []int{4, 2, 3, 1}
10	sort.Ints(s)
11	fmt.Println(s)
12}
1$ go run main.go
2[1 2 3 4]

1.8.12 فرق بین آرایه و slice #

  1. فرق نوع تعریف آرایه و slice
    • برای تعریف آرایه شما باید داخل براکت [] یک مقداری را قرار دهید.
    • برای تعریف slice هیچ مقداری را داخل براکت [] نباید قرار دهید.
1array := [3]int{10, 20, 30}
2
3slice := []int{10, 20, 30}
  1. فرق بین خالی بودن آرایه و slice
    • مقدار خالی یک slice هست nil
    • مقدار خالی یک آرایه, همان آرایه با المنت های مقدار پیش فرض می باشد.
1var slice []int32
2var array [2]int32
3
4fmt.Println(slice == nil) // print true
5fmt.Println(len(slice)) // print 0
6fmt.Println(cap(slice)) // print 0
7
8fmt.Println(array) // print [0 0]