Chào mừng đến với bài hướng dẫn số 15 trong series Hướng dẫn lập trình Golang cơ bản. Trong bài này chúng ta sẽ nói về con trỏ - Pointer.
Con trỏ là gì?
Một con trỏ là một biến chứa địa chỉ bộ nhớ của biến khác.
Nhìn vào hình trên, biến b có giá trị 156 và được lưu tại địa chỉ 0x1040a124. Biến a lưu địa chỉ của biến b, và được gọi là trỏ đến b.
Khai báo con trỏ
*T là kiểu biến con trỏ mà trỏ đến một giá trị của kiểu T.
Chúng ta hãy thử code:
package main
import (
"fmt"
)
func main() {
b := 255
var a *int = &b
fmt.Printf("Type of a is %T\n", a)
fmt.Println("address of b is", a)
}
Toán tử & được sử dụng để lấy địa chỉ của một biến. Tại dòng 9 của chương trình trên, chúng ta đang gán địa chỉ của biến b cho a có kiểu *int. Như vậy có thể nói a được trỏ đến b. Khi in giá trị của a, địa chỉ của b sẽ được in ra như sau:
Type of a is *int
address of b is 0x1040a124
Do giá trị của b có thể là bất cứ đâu trong bộ nhớ, mỗi lần truy cập các bạn có thể có một địa chỉ khác của biến b.
Giá trị ZERO của con trỏ
Giá trị zero của một con trỏ là nil.
package main
import (
"fmt"
)
func main() {
a := 25
var b *int
if b == nil {
fmt.Println("b is", b)
b = &a
fmt.Println("b after initialization is", b)
}
}
Trong ví dụ trên, biến b được khởi tạo là nil, sau đó được gán vào địa chỉ của biến a. Ví dụ trên sẽ xuất ra:
b is <nil>
b after initialisation is 0x1040a124
Tham chiếu ngược (dereferencing a pointer)
Tham chiếu ngược một con trỏ nghĩa là truy cập vào giá trị của biến mà con trỏ trỏ tới. *a là cú pháp tham chiếu ngược đến a.
Hãy xem thử ví dụ dưới đây:
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
}
Tại dòng 10 ví dụ trên, chúng ta tham chiếu ngược a và in ra giá trị của nó. Thật vậy, chương trình sẽ in ra giá trị của b:
address of b is 0x1040a124
value of b is 255
Chúng ta thử viết một ví dụ khác trong đó thay đổi giá trị của biến b sử dụng con trỏ.
package main
import (
"fmt"
)
func main() {
b := 255
a := &b
fmt.Println("address of b is", a)
fmt.Println("value of b is", *a)
*a++
fmt.Println("new value of b is", b)
}
Tại dòng 12 của ví dụ trên, chúng ta tăng giá trị được trỏ đến bởi biến a bằng 1, điều này sẽ thay đổi giá trị của biến b do a trỏ đến b. Do đó giá trị mới của b sẽ là 256, chương trình sẽ xuất ra:
address of b is 0x1040a124
value of b is 255
new value of b is 256
Sử dụng con trỏ trong hàm
package main
import (
"fmt"
)
func change(val *int) {
*val = 55
}
func main() {
a := 58
fmt.Println("value of a before function call is",a)
b := &a
change(b)
fmt.Println("value of a after function call is", a)
}
Trong ví dụ trên, tại dòng 14 chúng ta đưa vào con trỏ biến b lưu địa chỉ của a vào hàm change. Trong hàm change, giá trị của a bị thay đổi bằng cách tham chiếu ngược trong dòng 8. Chương trình xuất ra:
value of a before function call is 58
value of a after function call is 55
Hãy sử dụng con trỏ cho slice thay vì array trong hàm
Giả định rằng chúng ta cần sửa đổi một array trong hàm và những thay đổi đó hiển thị với caller. Để làm điều này, ta sử dụng con trỏ cho array như một đối số trong hàm:
package main
import (
"fmt"
)
func modify(arr *[3]int) {
(*arr)[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
Tại dòng 13 ví dụ trên, chúng ta đưa vào địa chỉ của array a trong hàm modify. Tại dòng 8, chúng ta tham chiếu ngược arr và gán 90 cho thành phần đầu tiên của array. Chương trình sẽ xuất ra
[90 90 91]
a[x] là cách viết gọn của (*a)[x]. Nên (*a)[0] trong ví dụ trên có thể được thay thế bởi arr[0]. Hãy thử viết lại ví dụ trên bằng cú pháp ngắn gọn:
package main
import (
"fmt"
)
func modify(arr *[3]int) {
arr[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(&a)
fmt.Println(a)
}
Chương trình trên sẽ xuất ra:
[90 90 91]
Mặc dù cách này sử dụng con trỏ cho array như một đối số trong hàm và thực hiện thay đổi nó, tuy nhiên vẫn không được coi là phương pháp "chuẩn" trong Go. Chúng ta hãy dùng slice thay vì array.
Thử viết lại chương trình trên sử dụng slice.
package main
import (
"fmt"
)
func modify(sls []int) {
sls[0] = 90
}
func main() {
a := [3]int{89, 90, 91}
modify(a[:])
fmt.Println(a)
}
Tại dòng 13 ví dụ trên, chúng ta đưa vào một slice trong hàm modify. Phần tử đầu tiên của slice được sửa thành 90 trong hàm modify. Chương trình xuất ra:
[90 90 91]
Do vậy, hãy quên con trỏ cho array, thay vào đó sử dụng slice, đoạn code sẽ tường minh hơn và là "chuẩn mực" trong Go :).
Go không hỗ trợ phép toán số học trên con trỏ
Go không hỗ trợ phép toán số học trên con trỏ giống nhiều ngôn ngữ lập trình khác như C.
Xét ví dụ dưới đây:
package main
func main() {
b := [...]int{109, 110, 111}
p := &b
p++
}
Chương trình sẽ trả về lỗi
main.go:6: invalid operation: p++ (non-numeric type *[3]int)
Phần hướng dẫn về con trỏ đến đây là kết thúc. Các bạn có thể truy cập github để xem các ví dụ mẫu.
Xin vui lòng để lại những ý kiến và phản hồi của các bạn. Chúc một ngày tốt lành!
Hẹn gặp lại trong bài học tiếp theo: Structures.
Bình luận