Go – Slice

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ố lowhigh 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ố lowhigh 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()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 slice1slice2 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

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.