承上啟下
? ? ? ? 我們在前面的文章中,首先介紹了GO的基礎語法,然后介紹了Goroutine和channel這個最具有特色的東西,同時介紹了Sync和context,以及在上篇文章中詳細距離說明了Go里面用于高并發的多種寫法。基礎的使用方法也告一段落了,我們要進入新的篇章,就是Go的指針,這邊的指針類型不僅是一個地址,還有Unsafe.Point,還有intptr,讓我們詳細看看。
開始學習
普通指針
在Go語言中,指針是一種特殊類型的變量,它存儲了另一個變量的內存地址。指針在Go中雖然不像C或C++那樣普遍使用,但它們在某些情況下仍然非常有用,尤其是在需要修改函數內部變量的值、避免大對象復制、實現數據結構(如鏈表、樹等)時。
以下是關于Go語言中指針的詳細介紹:
指針的基本概念
- 內存地址:每個變量在內存中都有一個地址,指針變量存儲的就是這個地址。
- 解引用:通過指針訪問它所指向的變量的值稱為解引用。
聲明指針
在Go中,指針的聲明方式是在變量類型前加上*
:
var pointer *int
這里,pointer
是一個指向int
類型變量的指針。
初始化指針
指針必須在使用前進行初始化。你可以使用&
操作符來獲取一個變量的地址,并將其賦值給指針:
value := 10
pointer := &value
在這個例子中,pointer
存儲了變量value
的內存地址。
解引用指針
使用*
操作符可以解引用指針,訪問或修改它所指向的值:
*pointer = 20
這將把變量value
的值修改為20。
函數中的指針
在函數中傳遞指針允許你修改函數外部的變量:
func modifyValue(ptr *int) {*ptr = 30
}func main() {value := 10modifyValue(&value)fmt.Println(value) // 輸出 30
}
在這個例子中,modifyValue
函數通過指針參數修改了外部變量value
的值。
指針的nil值
一個未初始化的指針有一個nil值,表示它不指向任何地址:
var pointer *int
if pointer == nil {fmt.Println("Pointer is nil")
}
指針和結構體
指針常用于結構體,可以創建結構體的指針,并通過指針訪問或修改結構體的字段:
type Person struct {Name stringAge int
}func main() {person := &Person{Name: "Alice", Age: 30}person.Age = 31 // 通過指針修改結構體的字段
}
指針數組與數組指針
-
指針數組:一個數組,其元素是指針。
var ptrArray [3]*int
-
數組指針:一個指向數組的指針。
var array [3]int var ptrToArray *[3]int = &array
指針的指針
雖然不常見,但你可以在Go中創建指向指針的指針:
var value int = 100
var ptr *int = &value
var ptrToPtr **int = &ptr
這里,ptrToPtr
是一個指向ptr
指針的指針。
注意事項
- Go不支持指針算術,即你不能對指針進行加減操作。
- Go的垃圾回收機制會自動管理內存,因此通常不需要手動釋放指針指向的內存。
Unsafe.Point
在Go語言中,unsafe.Pointer
?是一個特殊類型的指針,它可以指向任意類型的值。unsafe
?包提供了一些繞過Go類型系統的功能,允許程序進行一些原本不被允許的操作,比如在不同指針類型之間進行轉換,或者計算一個對象的實際內存大小等。
以下是關于?unsafe.Pointer
?的一些關鍵點:
類型轉換
unsafe.Pointer
?可以用于在任意指針類型之間進行轉換。例如,如果你有一個?*int
?類型的指針,你可以將其轉換為?unsafe.Pointer
,然后再轉換回其他類型的指針。
package mainimport ("fmt""unsafe"
)func main() {i := 42ip := &i // *intptr := unsafe.Pointer(ip) // 轉換為 unsafe.Pointeruptr := uintptr(ptr) // 轉換為 uintptr// 反向轉換ptr = unsafe.Pointer(uptr) // 轉換回 unsafe.Pointerip2 := (*int)(ptr) // 轉換回 *int*ip2 = 84fmt.Println(i) // 輸出 84
}
訪問任意內存地址
通過?unsafe.Pointer
,你可以訪問任意內存地址,這在Go的常規操作中是不被允許的,因為它繞過了Go的類型系統和內存安全檢查。
ptr := unsafe.Pointer(uintptr(0x12345678))
上述代碼試圖訪問一個特定的內存地址,這在實際的程序中是非常危險的,因為它可能導致未定義行為,包括程序崩潰。
計算結構體大小
unsafe.Sizeof
?函數可以返回一個值的大小,單位是字節。這個函數通常與?unsafe.Pointer
?一起使用來計算結構體的大小。
type MyStruct struct {a intb string
}s := MyStruct{a: 1, b: "hello"}
size := unsafe.Sizeof(s)
fmt.Println(size) // 輸出結構體 MyStruct 的大小
使用?uintptr
?進行指針算術
雖然Go不支持?unsafe.Pointer
?的算術操作,但你可以將?unsafe.Pointer
?轉換為?uintptr
,然后在?uintptr
?上執行算術操作,最后再轉換回?unsafe.Pointer
。
ptr := unsafe.Pointer(&s)
uptr := uintptr(ptr)
newPtr := unsafe.Pointer(uptr + unsafe.Offsetof(s.b)) // 訪問結構體中的 b 字段
注意事項
- 使用?
unsafe
?包繞過Go的類型系統和內存安全機制,需要非常小心,因為錯誤的使用可能會導致程序崩潰或者安全漏洞。 unsafe.Pointer
?的使用應該限制在必要的范圍內,并且要確保操作的安全性。unsafe
?包的內容可能會在不同的Go版本之間發生變化,因此在使用時應保持謹慎。
由于?unsafe
?包的功能非常強大,Go官方建議開發者只有在沒有其他選擇的情況下才使用它,并且要確保代碼的穩定性和安全性。
UintPtr
在Go語言中,intptr
?并不是一個內置的類型。你可能在提到?uintptr
?時出現了誤解,或者是在引用其他語言中的類型。在Go語言中,與指針操作相關的類型是?uintptr
。
uintptr
?類型
uintptr
?是?unsafe
?包中的一個類型,它足夠大,可以存儲任何類型的指針的位模式(即內存地址)。uintptr
?類型主要用于低級編程,比如與操作系統接口、內存操作等。
以下是一些關于?uintptr
?的關鍵點:
uintptr
?是一個無符號整數類型,其大小足以容納任何指針的位模式。- 它可以用于將指針轉換為整數,反之亦然。
uintptr
?可以用于執行指針算術,但這樣做需要非常小心,因為它可能會繞過Go的內存安全保證。
示例
以下是如何使用?uintptr
?的示例:
package mainimport ("fmt""unsafe"
)func main() {i := 42ptr := &i // *intuptr := uintptr(unsafe.Pointer(ptr)) // 轉換為 uintptr// 使用 uintptr 進行指針算術newPtr := unsafe.Pointer(uptr + unsafe.Sizeof(i))// 反向轉換回指針類型newIntPtr := (*int)(newPtr)*newIntPtr = 84fmt.Println(i) // 輸出 84
}
在這個例子中,我們首先將一個?*int
?類型的指針轉換為?uintptr
,然后執行了指針算術操作(雖然在這個特定的例子中這樣做沒有意義,因為它只是在一個整數大小的范圍內移動),最后將結果轉換回?*int
?類型的指針。
注意事項
- 使用?
uintptr
?需要非常小心,因為不正確的使用可能會導致內存安全問題,比如訪問未分配的內存、越界訪問等。 uintptr
?類型的值不應該被存儲或以任何方式保留,因為它們可能會在垃圾回收期間變得無效。- 通常情況下,Go程序員不需要直接使用?
uintptr
,除非他們正在編寫需要直接與操作系統或硬件交互的底層代碼。
三種指針類型對比
在Go語言中,指針、unsafe.Pointer
?和?uintptr
?是三種不同的概念,它們在內存操作和類型轉換中扮演著不同的角色。下面是它們的區別:
指針(如?*int
)
- 定義:指針是一種變量,它存儲了另一個變量的內存地址。
- 用途:用于引用和修改變量,或者在函數調用中傳遞變量的地址以修改其值。
- 類型安全:指針是類型安全的,它們只能指向特定類型的變量。
- 示例:
var a int = 42 var ptr *int = &a *ptr = 100 // 修改a的值
unsafe.Pointer
- 定義:
unsafe.Pointer
?是?unsafe
?包中的一個特殊類型,它可以指向任意類型的變量。 - 用途:用于在不同指針類型之間進行轉換,或者在需要時進行底層的內存操作。
- 類型安全:
unsafe.Pointer
?本身不是類型安全的,因為它可以指向任何類型的變量,但它需要與其他類型安全的指針一起使用。 - 示例:
var a int = 42 var ptr unsafe.Pointer = unsafe.Pointer(&a)
uintptr
- 定義:
uintptr
?是?unsafe
?包中的一個無符號整數類型,其大小足以存儲任何類型的指針的位模式。 - 用途:用于執行指針算術操作,或者將指針轉換為整數以便進行低級內存操作。
- 類型安全:
uintptr
?不是類型安全的,因為它可以存儲任何指針的位模式,并且可以用于執行指針算術,這可能會繞過Go的內存安全保證。 - 示例:
var a int = 42 var ptr uintptr = uintptr(unsafe.Pointer(&a))
區別
-
類型安全:
- 指針是類型安全的,只能用于指向特定類型的變量。
unsafe.Pointer
?不是類型安全的,可以指向任何類型的變量,但它需要與其他類型安全的指針一起使用。uintptr
?也不是類型安全的,它可以存儲任何指針的位模式,并且可以用于執行指針算術。
-
用途:
- 指針主要用于變量引用和修改變量的值。
unsafe.Pointer
?用于在不同指針類型之間進行轉換,或者在需要時進行底層的內存操作。uintptr
?用于執行指針算術操作,或者將指針轉換為整數以便進行低級內存操作。
-
內存安全:
- 使用指針時,Go的垃圾回收器會確保指向的變量在需要時不會被回收。
- 使用?
unsafe.Pointer
?和?uintptr
?時,程序員需要確保操作不會導致內存安全問題,比如越界訪問或訪問未分配的內存。
總結來說,指針是Go中用于日常變量引用和修改變量的類型安全工具,而?unsafe.Pointer
?和?uintptr
?用于更底層的內存操作,它們提供了更大的靈活性和能力,但同時也帶來了更高的風險,因為它們不是類型安全的,并且需要程序員更加小心地使用。