Go – Con trỏ

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.")
	}
}

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.