Trong ngôn ngữ lập trình Go, một số bài toán được thực hiện đơn giản hơn khi sử dụng con trỏ, như là gọi bằng tham chiếu sẽ không thể thực hiên được nếu không sử dụng con trỏ.
Như bạn đã biết, một biến là một vị trí bộ nhớ và tất cả vị trí bộ nhớ đều có địa chỉ được định nghĩa và có thể truy cập được bằng cách sử dụng toán tử ký hiệu &
, biểu thị một địa chỉ trong bộ nhớ.
Con trỏ là một biến dùng để lưu trữ địa chỉ bộ nhớ của biến khác. Bất cứ khi nào chúng ta cần viết một chương trình đều cần lưu trữ dữ liệu, thông tin vào trong bộ nhớ. Dữ liệu được lưu trong bộ nhớ tại một địa chỉ cụ thể. Địa chỉ bộ nhớ có định dạng như là 0xAFFFF
(dạng hexa). Để truy cập được dữ liệu, chúng ta cần biết địa chỉ nơi nó được lưu trữ. Chúng ta có thể kiểm soát được toàn bộ địa chỉ bộ nhớ nơi dữ liệu của chương trình được lưu trữ
Con trỏ là một dạng biến đặc biệt, bởi vì dữ liệu nó lưu trữ không phải là giá trị thông thường như là integer hoặc string, nó là địa chỉ của biến khác
Trong ví dụ bên trên, con trỏ p
chứa giá trị 0x0001
, là địa chỉ của biến a
Khai báo con trỏ
Một con trỏ kiểu T được khai báo với cú pháp như sau
var p *T
Kiểu T là kiểu của biến con trỏ p
trỏ đến, ví dụ chúng ta có con trỏ kiểu int
như sau
var p *int
Con trỏ bên trên có thể chỉ lưu trữ địa chỉ bộ nhớ của các biến int
Giá trị 0 của con trỏ là nil
. Nghĩa là bất kỳ con trỏ nào chưa được khởi tạo sẽ có giá trị nil
.
package main import "fmt" func main() { var p *int fmt.Println("p = ", p) }
Kết quả
p = <nil>
Khởi tạo con trỏ
Bạn có thể khởi tạo một con trỏ với địa chỉ bộ nhớ của biến khác. Địa chỉ bộ nhớ của biến có thể nhận được bằng cách sử dụng toán tử &
.
var x = 100 var p *int = &x
Chú ý cách chúng ta sử dụng toán tử &
với biến x
để lấy được địa chỉ của nó và sau đó gán địa chỉ cho con trỏ p
.
Cũng giống như bất kỳ biến nào khác trong Go, kiểu của biến con trỏ p
cũng được suy ra bởi trình biên dịch. Nên bạn có thể bỏ qua việc khai báo kiểu cho con trỏ cho ví dụ bên trên và viết lại như sau
var p = &a
Hãy cùng xem ví dụ sau để hiểu rõ hơn
package main import "fmt" func main() { var a = 5.67 var p = &a fmt.Println("Value stored in variable a = ", a) fmt.Println("Address of variable a = ", &a) fmt.Println("Value stored in variable p = ", p) }
Kết quả
Value stored in variable a = 5.67 Address of variable a = 0xc4200120a8 Value stored in variable p = 0xc4200120a8
Tham chiếu ngược con trỏ
Bạn có thể sử dụng toán tử *
trên con trỏ để truy cập đến giá trị được lưu trữ trong biến con trỏ đang trỏ đến. Nó được gọi là dereferencing (tham chiếu ngược) hoặc indirecting (gián tiếp)
package main import "fmt" func main() { var a = 100 var p = &a fmt.Println("a = ", a) fmt.Println("p = ", p) fmt.Println("*p = ", *p) }
Kết quả
a = 100 p = 0xc4200120a8 *p = 100
Bạn có thể không chỉ truy cập đến giá trị của biến được trỏ đến sử dụng toán tử *
, bạn cũng có thể thay đổi giá trị của nó. Ví dụ sau đây sẽ sửa giá trị được lưu trữ trong biến a
thông qua con trỏ p
package main import "fmt" func main() { var a = 1000 var p = &a fmt.Println("a (before) = ", a) // Thay đổi giá trị được lưu trữ trong biến trỏ đến thông qua con trỏ *p = 2000 fmt.Println("a (after) = ", a) }
Kết quả
a (before) = 1000 a (after) = 2000
Tạo con trỏ sử dụng hàm new()
Bạn cũng có thể tạo con trỏ sử dụng hàm tích hợp sẵn new()
. Hàm new()
nhận kiểu như một tham số, cấp phát đủ bộ nhớ để chứa giá trị của kiểu, và trả về một con trỏ cho nó.
package main import "fmt" func main() { ptr := new(int) // Con trỏ cho kiểu `int` *ptr = 100 fmt.Printf("Ptr = %#x, Ptr value = %d\n", ptr, *ptr) }
Kết quả
Ptr = 0xc420014058, Ptr value = 100
Con trỏ cho con trỏ
Một con trỏ có thể trỏ đến biến với kiểu bất kỳ. Nó cũng có thể trỏ đến con trỏ khác. Ví dụ sau đây biểu diễn cách tạo con trỏ cho con trỏ khác.
package main import "fmt" func main() { var a = 7.98 var p = &a var pp = &p fmt.Println("a = ", a) fmt.Println("address of a = ", &a) fmt.Println("p = ", p) fmt.Println("address of p = ", &p) fmt.Println("pp = ", pp) // Tham chiếu ngược con trỏ đến con trỏ fmt.Println("*pp = ", *pp) fmt.Println("**pp = ", **pp) }
Kết quả
a = 7.98 address of a = 0xc4200120a8 p = 0xc4200120a8 address of p = 0xc42000c028 pp = 0xc42000c028 *pp = 0xc4200120a8 **pp = 7.98
Toán học con trỏ không hỗ trợ trong Go
Nếu bạn đã từng làm việc với các ngôn ngữ c\c+, bạn sẽ biết rằng các ngôn ngữ này hỗ trợ toán học con trỏ. Ví dụ bạn có thể tăng hoặc giảm con trỏ để có thể di chuyển lên phía trước hoặc lùi về sau địa chỉ bộ nhớ. Bạn có thể cộng hoặc trừ giá trị cho hoặc từ con trỏ. Bạn cũng có thể so sánh 2 con trỏ bằng cách sử dụng toán tử quan hệ như là ==
, <
, >
…
Nhưng Go không hỗ trợ các phép tính toán học như thế trên con trỏ. Nếu sử dụng phép tính này trên Go thì trình biên dịch sẽ báo lỗi
package main func main() { var x = 67 var p = &x var p1 = p + 1 // Compiler Error: invalid operation }
Tuy nhiên bạn có thể so sánh 2 con trỏ cùng kiểu sử dụng toán tử ==
package main import "fmt" func main() { var a = 75 var p1 = &a var p2 = &a if p1 == p2 { fmt.Println("Both pointers p1 and p2 point to the same variable.") } }