Go Slice là một abstraction ở trên mảng Go. Mảng của Go cho phép chúng ta định nghĩa các biến có thể nắm giữ một số các phần tử cùng kiểu dữ liệu nhưng không cung cấp bất kỳ phương thức nào giúp tăng kích thước của nó một cách linh hoạt hoặc lấy một mảng con mà nó sở hữu. Slice loại bỏ giới hạn này. Nó cung cấp nhiều hàm tiện ích cần thiết trên mảng và được sử dụng rộng rãi trong Go.
Cũng giống như mảng, Slice được đánh index và có độ dài. Nhưng không giống như mảng, kích thước của nó có thể thay đổi.
Khai báo Slice
Slice kiểu T
được khai báo sử dụng []T
giống như khai báo một mảng nhưng không chỉ định rõ kích thước. Ngoài ra, chúng ta có thể dùng hàm make
để tạo một Slice.
var numbers []int /* Slice không chỉ định kích thước */ /* numbers == []int{0,0,0,0,0}*/ numbers = make([]int,5,5) /* độ dài 5, chứa 5*/
Chúng ta có thể tạo một Slice sử dụng literal như sau
var s = []int{3, 5, 7, 9, 11, 13, 17}
Biểu thức ở phía bên phải của câu lệnh bên trên là một literal. Slice literal được khai báo tương tự như một mảng literal, ngoại trừ việc bạn chỉ định kích thước trong dấu ngoặc.
Khi bạn tạo một Slice sử dụng literal, trước tiên nó sẽ tạo một mảng và sau đó trả về một Slice tham chiếu đến nó.
Hãy cùng xem ví dụ sau
package main import "fmt" func main() { // tạo một Slice sử dụng literal var s = []int{3, 5, 7, 9, 11, 13, 17} // khai báo tắt t := []int{2, 4, 8, 16, 32, 64} fmt.Println("s = ", s) fmt.Println("t = ", t) }
Kết quả
s = [3 5 7 9 11 13 17] t = [2 4 8 16 32 64]
Tạo Slice từ một mảng
Vì Slice là một phân đoạn của mảng, nên chúng ta có thể tạo Slice từ mảng.
Để tạo một Slice từ một mảng a
, chúng ta chỉ định 2 chỉ số low
và high
phân biệt bằng dấu hai chấm.// tạo slice từ mảng a
a[low:high]
Biểu thức trên lựa chon một Slice từ mảng a
. Kết quả Slice bao gồm tất cả các phần tử bắt đầu từ index low
đến high
, nhưng không bao gồm phần tử ở index high
Hãy cũng xem ví dụ sau đây để hiểu rõ hơn
package main import "fmt" func main() { var a = [5]string{"Alpha", "Beta", "Gamma", "Delta", "Epsilon"} // Tạo một slice từ một mảng var s []string = a[1:4] fmt.Println("Array a = ", a) fmt.Println("Slice s = ", s) }
Kết quả
Array a = [Alpha Beta Gamma Delta Epsilon] Slice s = [Beta Gamma Delta]
Chỉ số low
và high
trong biểu thức Slice là không bắt buộc. Mặc định giá trị của low
là 0, và high
là độ dài của Slice.
package main import "fmt" func main() { a := [5]string{"C", "C++", "Java", "Python", "Go"} slice1 := a[1:4] slice2 := a[:3] slice3 := a[2:] slice4 := a[:] fmt.Println("Array a = ", a) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) fmt.Println("slice3 = ", slice3) fmt.Println("slice4 = ", slice4) }
Kết quả
Array a = [C C++ Java Python Go] slice1 = [C++ Java Python] slice2 = [C C++ Java] slice3 = [Java Python Go] slice4 = [C C++ Java Python Go]
Tạo Slice từ Slice khác
Slice cũng có thể được tạo từ Slice đang tồn tại.
package main import "fmt" func main() { cities := []string{"New York", "London", "Chicago", "Beijing", "Delhi", "Mumbai", "Bangalore", "Hyderabad", "Hong Kong"} asianCities := cities[3:] indianCities := asianCities[1:5] fmt.Println("cities = ", cities) fmt.Println("asianCities = ", asianCities) fmt.Println("indianCities = ", indianCities) }
Kết quả
cities = [New York London Chicago Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong] asianCities = [Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong] indianCities = [Delhi Mumbai Bangalore Hyderabad]
Sửa đổi một Slice
Slice là kiểu tham chiếu. Chúng tham chiếu đến một mảng cơ sở. Chỉnh sửa các phần tử trong Slice tức là thay đổi các phần tử tương ứng tham chiếu đến mảng. Các Slice khác cùng tham chiếu đến mảng này sẽ thay đổi theo
package main import "fmt" func main() { a := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"} slice1 := a[1:] slice2 := a[3:] fmt.Println("------- Trước khi sửa -------") fmt.Println("a = ", a) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) slice1[0] = "TUE" slice1[1] = "WED" slice1[2] = "THU" slice2[1] = "FRIDAY" fmt.Println("\n-------- Sau khi sửa --------") fmt.Println("a = ", a) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) }
Kết quả
------- Trước khi sửa ------- a = [Mon Tue Wed Thu Fri Sat Sun] slice1 = [Tue Wed Thu Fri Sat Sun] slice2 = [Thu Fri Sat Sun] -------- Sau khi sửa -------- a = [Mon TUE WED THU FRIDAY Sat Sun] slice1 = [TUE WED THU FRIDAY Sat Sun] slice2 = [THU FRIDAY Sat Sun]
Độ dài và dung lượng của Slice
Một Slice bao gồm 3 thứ sau:
- Con trỏ (Ptr) tham chiếu đến một mảng cơ sở
- Độ dài (len) phân đoạn của mảng mà slice chứa
- Dung lượng (cap) (Kích thước tối đa mà phân đoạn có thể tăng)
Hãy cùng xem mảng và Slice sau đây thu được
var a = [6]int{10, 20, 30, 40, 50, 60} var s = [1:4]
Chúng ta có thể miêu tả Slice s
như sau
Độ dài của Slice là số phần tử trong Slice, là 3 ở ví dụ trên
Dung lượng là số phần tử trong mảng cơ sở bắt đầu từ phần tử đầu tiên trong Slice. Nó là 5 ở ví dụ trên.
Bạn có thể tìm độ dài và dung lượng của Slice bằng cách sử dụng hàm len()
và cap()
.
package main import "fmt" func main() { a := [6]int{10, 20, 30, 40, 50, 60} s := a[1:4] fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Kết quả
s = [20 30 40], len = 3, cap = 5
Độ dài của Slice có thể tăng bằng dung lượng của nó bằng cách cắt lại nó. Nếu chúng ta thử tăng độ dài của Slice lớn hơn dung lượng thì một lỗi được đưa ra khi chạy.
Hãy cùng xem ví dụ sau đây để hiểu cách chúng ta thay đổi độ dài của Slice và dung lượng
package main import "fmt" func main() { s := []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100} fmt.Println("Slice ban đầu") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[1:5] fmt.Println("\nSau khi cắt từ index 1 đến 5") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[:8] fmt.Println("\nSau khi tăng độ dài") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) s = s[2:] fmt.Println("\nSau khi giảm 2 phần tử đầu tiên") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Kết quả
Slice ban đầu s = [10 20 30 40 50 60 70 80 90 100], len = 10, cap = 10 Sau khi cắt từ index 1 đến 5 s = [20 30 40 50], len = 4, cap = 9 Sau khi tăng độ dài s = [20 30 40 50 60 70 80 90], len = 8, cap = 9 Sau khi giảm 2 phần tử đầu tiên s = [40 50 60 70 80 90], len = 6, cap = 7
Tạo Slice sử dụng hàm make()
Go cung cấp hàm thư viện được gọi là make()
dùng để tạo Slice
func make([]T, len, cap) []T
Hàm make()
nhận giá trị đầu vào là kiểu – []T
, độ dài – len
, dung lượng – cap
(không bắt buộc). Nó cấp phát một mảng cơ sở với kích thước bằng với tham số dung lượng – cap
truyền vào, và trả về một Slice tham chiếu đến mảng.
package main import "fmt" func main() { // tạo một mảng kích thước là 10, độ dài 5, và trả về một Slice tham chiếu s := make([]int, 5, 10) fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Kết quả
s = [0 0 0 0 0], len = 5, cap = 10
Tham số dung lượng – cap
trong hàm make()
là không bắt buộc. Khi bị bỏ qua, mặc định của nó xác định theo độ dài
package main import "fmt" func main() { // tạo mảng với độ dài 5, và trả về Slice tham chiếu đến nó s := make([]int, 5) fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Kết quả
s = [0 0 0 0 0], len = 5, cap = 5
Giá trị 0 của Slice
Giá trị 0 của Slice là nil
. Một nil
Slice không có bất kỳ mảng cơ sở nào, và có độ dài và dung lượng bằng 0.
package main import "fmt" func main() { var s []int fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) if s == nil { fmt.Println("s is nil") } }
Kết quả
s = [], len = 0, cap = 0 s is nil
Các hàm Slice
Hàm copy(): Sao chép một Slice
Hàm copy()
sao chép tất cả phần tử của một Slice sang Slice khác.
func copy(dst, src []T) int
Nó nhận vào 2 tham số: dst
– Slice đích, src
– Slice gốc. Sao chép tất cả các phần tử của gốc sang Slice đích và trả về số phần tử đã được sao chép.
Số phần tử được sao chép sẽ bằng độ dài của Slice nhỏ hơn của Slice gốc – len(src)
và Slice đích – len(dst)
package main import "fmt" func main() { src := []string{"Sublime", "VSCode", "IntelliJ", "Eclipse"} dest := make([]string, 2) numElementsCopied := copy(dest, src) fmt.Println("src = ", src) fmt.Println("dest = ", dest) fmt.Println("Number of elements copied from src to dest = ", numElementsCopied) }
Kết quả
src = [Sublime VSCode IntelliJ Eclipse] dest = [Sublime VSCode] Number of elements copied from src to dest = 2
Hàm append(): Nối vào một slice
Hàm append()
dùng để nối thêm các phần tử mới vào đuôi của Slice cho trước.
func append(s []T, x ...T) []T
Nó nhận vào một Slice và một số lượng các đối số x ...T
. Sau đó trả về một Slice mới chứa tất cả các phần tử của Slice cho trước cũng như các phần tử mới.
Nếu Slice cho trước không có đủ dung lượng để chứa các phần tử mới thì một mảng cơ sở mới sẽ được cấp phát với dung lượng lớn hơn. Tất cả các phần tử từ mảng cơ sở của Slice hiên tại được sao chép sang mảng mới này, sau đó các phần tử mới được nối thêm vào.
Tuy nhiên, nếu Slice có đủ dung lượng để chứa các phần tử mới, thì hàm append()
sử dụng lại mảng cơ sở và thêm các phần tử mới vào chính mảng hiện tại.
package main import "fmt" func main() { slice1 := []string{"C", "C++", "Java"} slice2 := append(slice1, "Python", "Ruby", "Go") fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1)) fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2)) slice1[0] = "C#" fmt.Println("\nslice1 = ", slice1) fmt.Println("slice2 = ", slice2) }
Kết quả
slice1 = [C C++ Java], len = 3, cap = 3 slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 6 slice1 = [C# C++ Java] slice2 = [C C++ Java Python Ruby Go]
Trong ví dụ trên, slice1
có dung lượng là 3, nó không thể chứa thêm phần tử. Nên mảng cơ sở mới được cấp phát với dung lượng lớn hơn khi chúng ta thêm phần tử mới cho nó.
Nếu chúng ta thay đổi slice1
và slice2
sẽ không thấy những thay đổi ảnh hưởng đến nhau bởi vì chúng đang được tham chiếu đến 2 mảng khác nhau.
Nhưng chuyện gì xảy ra nếu slice1
có đủ dung lượng để chứa các phần tử mới? Trong trường hợp này, sẽ không có mảng mới nào được cấp phát, và các phần tử được thêm vào mảng cơ sở hiện tại. Do đó, trong trường hợp này, những thay đổi trong slice1
sẽ ảnh hưởng đến slice2
.
package main import "fmt" func main() { slice1 := make([]string, 3, 10) copy(slice1, []string{"C", "C++", "Java"}) slice2 := append(slice1, "Python", "Ruby", "Go") fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1)) fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2)) slice1[0] = "C#" fmt.Println("\nslice1 = ", slice1) fmt.Println("slice2 = ", slice2) }
Kết quả
slice1 = [C C++ Java], len = 3, cap = 10 slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 10 slice1 = [C# C++ Java] slice2 = [C# C++ Java Python Ruby Go]
Nối vào một Slice nil
Khi bạn thêm vào các giá trị cho một Slice nil
, nó cấp phát một Slice mới và trả về tham chiếu đến một Slice mới.
package main import "fmt" func main() { var s []string // Nối một Slice nil s = append(s, "Cat", "Dog", "Lion", "Tiger") fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s)) }
Kết quả
s = [Cat Dog Lion Tiger], len = 4, cap = 4
Nối Slice với Slice khác
Bạn có thể nối một Slice với một Slice khác sử dụng toán tử ...
. Toán tử này giúp mở rộng Slice thành một danh sách các đối số.
package main import "fmt" func main() { slice1 := []string{"Jack", "John", "Peter"} slice2 := []string{"Bill", "Mark", "Steve"} slice3 := append(slice1, slice2...) fmt.Println("slice1 = ", slice1) fmt.Println("slice2 = ", slice2) fmt.Println("After appending slice1 & slice2 = ", slice3) }
Kết quả
slice1 = [Jack John Peter] slice2 = [Bill Mark Steve] After appending slice1 & slice2 = [Jack John Peter Bill Mark Steve]
Slice của Slice
Slice có thể là kiểu dữ liệu bất kỳ. Nên chúng ta có thể chứa các Slice khác.
package main import "fmt" func main() { s := [][]string{ {"India", "China"}, {"USA", "Canada"}, {"Switzerland", "Germany"}, } fmt.Println("Slice s = ", s) fmt.Println("length = ", len(s)) fmt.Println("capacity = ", cap(s)) }
Kết quả
Slice s = [[India China] [USA Canada] [Switzerland Germany]] length = 3 capacity = 3