Trong Go, hiểu cách các lát hoạt động khi được truyền vào các hàm là rất quan trọng để viết mã hiệu quả và không có lỗi. Hành vi này thường là nguồn gây nhầm lẫn cho nhiều nhà phát triển, đặc biệt là những người mới làm quen với ngôn ngữ này. Trong bài viết này, chúng ta sẽ khám phá sự khác biệt giữa việc truyền lát theo giá trị và theo tham chiếu, và cách nó ảnh hưởng đến việc sửa đổi các lát bên trong các hàm.
Giới thiệu
Trong Go, lát là một cấu trúc dữ liệu cơ bản được sử dụng để làm việc với chuỗi các phần tử. Chúng về cơ bản là một cái nhìn vào một mảng cơ sở, cung cấp một cách linh hoạt và mạnh mẽ để thao tác các tập hợp dữ liệu. Tuy nhiên, cách các lát được xử lý khi được truyền vào các hàm đôi khi có thể dẫn đến hành vi không mong muốn nếu không được hiểu đúng.
Truyền theo giá trị
Khi một lát được truyền vào một hàm trong Go, nó được truyền theo giá trị. Điều này có nghĩa là một bản sao của tiêu đề lát, bao gồm con trỏ, độ dài và dung lượng, được tạo ra và truyền vào hàm. Hãy xem xét hàm ví dụ sau:
Hãy bắt đầu bằng cách kiểm tra những gì xảy ra khi chúng ta truyền một lát vào một hàm theo giá trị. Hãy xem xét hàm sau:
func set3(a []int) {
a[1] = 3
}
Trong hàm này, chúng ta đang cố gắng đặt giá trị tại chỉ mục 1 của lát a
thành 3. Vì các lát được truyền theo giá trị trong Go, nên một bản sao của tiêu đề lát được truyền vào hàm, bao gồm cả con trỏ đến mảng cơ sở. Các sửa đổi được thực hiện đối với các phần tử của lát bên trong hàm sẽ ảnh hưởng đến lát gốc bên ngoài hàm.
Do đó, đầu ra cho đoạn mã bên dưới sẽ là [1 3 3 4 5].
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
set3(a)
fmt.Printf("%+v\n", a)
}
func set3(a []int) {
a[1] = 3
}
Trong ví dụ này, giá trị tại chỉ mục 1 của lát gốc a
được sửa đổi thành 3 bên trong hàm set3
. Khi chúng ta in a
sau khi gọi set3
, chúng ta thấy rằng việc sửa đổi được phản ánh trong lát gốc.
Bây giờ điều gì sẽ xảy ra nếu một phần tử được thêm vào lát được truyền?
func add3(a []int) {
a = append(a, 3)
}
Điều gì sẽ xảy ra với lát a
?
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2}
add3(a)
fmt.Printf("%+v\n", a)
}
func add3(a []int) {
a = append(a, 3)
}
Nó sẽ in [1 2 3] hay [1 2]?
Trong hàm này, chúng ta cố gắng thêm số 3 vào lát a
được cung cấp. Tuy nhiên, vì a
được truyền theo giá trị, nên bất kỳ sửa đổi nào được thực hiện đối với nó bên trong hàm đều cục bộ với phạm vi của hàm đó. Lát gốc được truyền vào add3
vẫn không thay đổi sau khi hàm trả về. Điều này vẫn đúng mặc dù lát được hỗ trợ bởi mảng trong Go.
Truyền theo tham chiếu
Để sửa đổi lát gốc bên trong một hàm, chúng ta có thể truyền một con trỏ đến lát. Bằng cách này, hàm nhận được một tham chiếu đến lát gốc, cho phép nó sửa đổi trực tiếp mảng cơ sở. Đây là một hàm ví dụ:
func add4(a *[]int) {
*a = append(*a, 4)
}
Trong hàm này, chúng ta nhận được một con trỏ đến một lát a
, và chúng ta thêm giá trị 4 vào lát được chỉ đến bởi a
. Bởi vì chúng ta đang sửa đổi lát gốc thông qua con trỏ, nên các thay đổi sẽ được phản ánh bên ngoài hàm.
Kết luận
Hiểu cách các lát hoạt động khi được truyền vào các hàm là điều cần thiết để viết mã Go chính xác và hiệu quả. Bằng cách nhận ra sự khác biệt giữa truyền theo giá trị và truyền theo tham chiếu, các nhà phát triển có thể tránh được những cạm bẫy phổ biến và viết mã dễ dự đoán hơn. Hãy nhớ rằng, các lát được truyền theo giá trị, nhưng vì chúng tham chiếu đến các mảng cơ sở, nên các sửa đổi được thực hiện bên trong các hàm có thể ảnh hưởng đến lát gốc nếu được truyền theo tham chiếu.