當我們在宣告變數時,電腦會為該變數在記憶體中分配一個位置,然後將這個變數值儲存在這個位置上,需要讀取或修改這個變數值時,電腦是透過記憶體位置來存取這個值。 今天來簡單介紹一下go的Pointer,他的特性以及常見用法。
Pointer是一種資料類型,用來儲存變數的記憶體地址。在Go中,我們可以通過使用 *
符號來宣告和操作Pointer。這允許我們直接訪問和修改變數的內容,而不僅僅是讀取或複製它們的值。
每個變數都有一個記憶體地址,我們可以使用Pointer變數來儲存這個地址。讓我們看一個範例:
package main
import "fmt"
func main() {
x := 42
var p *int // 宣告一個整數Pointer
p = &x // 將p指向x的地址
fmt.Println("x =", x)
fmt.Println("p =", p)
}
在這個例子中,我們創建了一個整數變數 x
,並宣告了一個整數Pointer p
,然後將 p
設為 x
的地址。現在,p
裡面存放的就是 x
的地址。
Go中的Pointer可以通過 new
函數來初始化,這將為指定的類型分配記憶體並返回其地址。範例:
package main
import "fmt"
func main() {
var p *int
p = new(int) // 初始化一個整數Pointer
*p = 123 // 將Pointer所指向的記憶體設置為123
fmt.Println("*p =", *p)
}
可以看到,我們創建了一個整數Pointer p
,並使用 new(int)
初始化它,然後將 p
所指向的記憶體設置為 123
。
通過Pointer,我們可以訪問和修改變數的值,這稱為Pointer的解引用。
package main
import "fmt"
func main() {
x := 42
p := &x // 將p設為x的地址,使用&
fmt.Println("x =", x)
fmt.Println("&x =", &x)
fmt.Println("*p =", *p) // 解引用Pointer,獲得x的值,使用*
*p = 99 // 通過Pointer修改x的值
fmt.Println("x =", x) // x的值已被修改
fmt.Println("&x =", &x)
fmt.Println("*p =", *p)
}
在這個例子中,我們使用Pointer p
解引用,並修改了變數 x
的值。也可以觀察到修改值並不會改變記憶體位置。
當需要處理大型資料結構時,使用Pointer可以節省記憶體,因為它們只儲存變數的地址,而不是整個資料。
package main
import (
"fmt"
"unsafe"
)
// 定義一個名為 Person 的結構(struct)
type Person struct {
Name string // 名稱
Age int // 年齡
}
func main() {
// 創建一個 Person 變數,名稱為 Alice,年齡為 30 歲
alice := Person{Name: "Alice", Age: 30}
// 計算 Person 變數 alice 的大小
personSize := unsafe.Sizeof(alice)
// 創建一個指向 Person 變數的Pointer,名稱為 Bob,年齡為 24 歲
bob := &Person{Name: "Bob", Age: 24}
// 計算指向 Person 變數 bob 的Pointer的大小
pointerSize := unsafe.Sizeof(bob)
fmt.Printf("alice -> Person 變數的大小: %d 個位元組\n", personSize)
fmt.Printf("bob -> 指向 Person 變數的Pointer的大小: %d 個位元組\n", pointerSize)
fmt.Println(&alice) // 列印 alice 變數的記憶體位置
fmt.Println(&bob) // 列印 bob 變數(Pointer)的記憶體位置
}
在上面的範例中,我們透過兩種方式創建person,但可以觀察到使用Pointer的方式創建,使用的大小較小,達到節省記憶體的作用。
想要在函數中修改變數的值,而不只是副本時,Pointer變得非常有用。
package main
import "fmt"
func modifyValue(x *int) {
*x = 100
}
func main() {
y := 10
modifyValue(&y) // 通過Pointer修改y的值
fmt.Println("y =", y)
}
在某些情況下,使用Pointer可以提高性能,因為它們允許直接訪問和修改記憶體,而不需要額外的記憶體複製操作。
package main
import (
"fmt"
"time"
)
const N = 1000000 // 陣列大小
// 使用值傳遞的函數,不使用指標
func sumValues(arr [N]int) int {
sum := 0
for _, v := range arr {
sum += v
}
return sum
}
// 使用指標傳遞的函數,避免陣列複製
func sumPointers(arr *[N]int) int {
sum := 0
for _, v := range arr {
sum += v
}
return sum
}
func main() {
// 創建一個包含大量資料的整數陣列
var arr [N]int
for i := 0; i < N; i++ {
arr[i] = i
}
// 測試不使用指標的情況,複製陣列
startTime := time.Now()
result1 := sumValues(arr)
duration1 := time.Since(startTime)
// 測試使用指標的情況,避免複製陣列
startTime = time.Now()
result2 := sumPointers(&arr)
duration2 := time.Since(startTime)
fmt.Printf("不使用指標的結果:%d,執行時間:%v\n", result1, duration1)
fmt.Printf("使用指標的結果:%d,執行時間:%v\n", result2, duration2)
}
可以看到,我們創建了一個包含大量資料的整數陣列,並寫了兩個函數來計算陣列中所有元素的總和。一個函數 sumValues
使用值傳遞陣列,另一個函數 sumPointers
使用指標傳遞陣列。
在 main
函數中,測試了這兩個函數的效能。結果顯示,使用指標傳遞陣列可以明顯提高效能,因為它少了陣列的複製,節省了大量的時間和記憶體。
Pointer是程式語言中的重要概念,它允許我們直接訪問和修改變數的內容,並在許多情況下提供性能優勢和節省記憶體的機會。但還是要小心使用Pointer,確保程式碼的穩定性和安全性。