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 := []int{}
4
5newSlice = 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]