文章目錄
- 1. 前言
- 2. 什麼是指針
- 3. 如何使用指針
- 4. Go 空指針(nil)
- 5. Go 指針數組
1. 前言
指針概念在 Go 語言中被拆分為兩個核心概念:
- 類型指針,允許對這個指針類型的資料進行修改。傳遞資料使用指針,而無須拷貝 資料。類型指針不能進行偏移和運算。
- 切片,由指向起始元素的原始指針、元素數量和容量組成。
受益于這樣的限制和拆分, Go 語言的指針類型變量擁有指針的高效通路,但又不會 發生指針偏移,進而避免非法修改關鍵性資料問題。同時, 垃圾回收也比較容易對不會發 生偏移的指針進行檢索和回收。 切片比原始指針具備更強大的特性, 更為安全。切片發生越界時,運作時會報出岩機,并打出堆枝,而原始指針隻會崩潰。 要明白指針,需要知道幾個概念:
指針位址、指針類型和指針取值
。
每個變量在運作時都擁有一個位址,這個位址代表變量在記憶體中的位置。 Go 語言中 使用“&” 操作符放在變量前面對變量進行“取位址”操作。
格式如下:
ptr : = &v
II v 的類型為 T 其中 v代表被取位址的變量,被取位址的V使用 陽變量進行接收,p仕的類型就為川T”, 稱做 T 的指針類型。 “*”代表指針
我們都知道,變量是一種使用友善的占位符,用于引用計算機記憶體位址。
Golang 支援指針類型 T,指針的指針 **T,以及包含包名字首的 .T。
- 預設值 nil,沒有 NULL 常量。
- 操作符 “&” (取位址符) 取變量位址
- “*” (取值符)透過指針通路目标對象。
- 不支援指針運算,不支援 “->” 運算符,直接用 “.” 通路目标成員。
變量在記憶體中位址:
package main
import "fmt"
func main(){
var a int =10
fmt.Printf("變量的位址: %x\n",&a)}
執行以上代碼輸出結果為:
變量的位址: c420012058
2. 什麼是指針
一個指針變量可以指向任何一個值的記憶體位址,它指向那個值的記憶體位址。
類似于變量和常量,在使用指針前你需要聲明指針。
指針聲明格式如下:
var name *類型
指針聲明:
package main
var ip *int /* 指向整型*///聲明一個int值得指針變量
var fp *float32 /* 指向浮點型 */
var sp *string /* 指向字元串類型 */
func main(){}
3. 如何使用指針
指針使用流程:
定義指針變量。
為指針變量指派。
通路指針變量中指向位址的值。
在指針類型前面加上 * 号(字首)來擷取指針所指向的内容。
$ cat pointer.go
package main
import "fmt"
func main(){
var a int =20/* 聲明實際變量 */
var ip *int /* 聲明指針變量 */
ip =&a /* 指針變量的存儲位址 */
fmt.Printf("a 變量的位址是: %x\n",&a)/* 指針變量的存儲位址 */
fmt.Printf("ip 變量的存儲位址: %x\n", ip)/* 使用指針通路值 */
fmt.Printf("*ip 變量的值: %d\n",*ip)}
[root@localhost go]# go run pointer.go
a 變量的位址是: c000082010
ip 變量的存儲位址: c000082010
*ip 變量的值: 20
直接用指針通路目标對象成員:
package main
import ("fmt")
func main(){
type data struct{ a int }
var d = data{1234}
var p *data
p =&d
fmt.Printf("%p, %v\n", p, p.a)// 直接用指針通路目标對象成員,無須轉換。}
輸出結果:
0xc420012058,1234
不能對指針做加減法等運算。
package main
func main(){
x :=1234
p :=&x
p++}
輸出結果:
./main.go:6:3: invalid operation: p++(non-numeric type *int)
可以在
unsafe.Pointer
和任意類型指針間進 轉換。
cat pointer2.go
package main
import (
"fmt"
"unsafe")
func main(){
x :=0x12345678
p := unsafe.Pointer(&x)// *int -> Pointer
n :=(*[4]byte)(p)// Pointer -> *[4]byte
for i :=0; i <len(n); i++{
fmt.Printf("%X ", n[i])}
}
[root@localhost pointer]# go run pointer2.go
78 56 34 12
将 Pointer 轉換成 uintptr,可變相實作指針運算。
$ cat pointer3.go
package main
import (
"fmt"
"unsafe"
)
func main(){
d := struct {
s string
x int
}{"abc",100}
p :=uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr
p += unsafe.Offsetof(d.x) // uintptr + offset
p2 := unsafe.Pointer(p) // uintptr -> Pointer
px :=(*int)(p2)
d.x = 200
fmt.Printf("%#v\n", px)
fmt.Printf("%#v\n", d)
}
[root@localhost pointer]# go run pointer3.go
(*int)(0xc00006af30)
struct { s string; x int }{s:"abc", x:200}
注意:GC 把 uintptr 當成普通整數對象,它無法阻止 “關聯” 對象被回收。
4. Go 空指針(nil)
當一個指針被定義後沒有配置設定到任何變量時,它的值為 nil。
nil 指針也稱為空指針。
nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。
一個指針變量通常縮寫為 ptr。
$ cat pointer4.go
package main
import "fmt"
func main(){
var ptr *int
fmt.Printf("ptr 的值為 : %x\n", ptr)}
[root@localhost pointer]# go run pointer4.go
ptr 的值為 : 0
空指針判斷:
$ cat pointer5.go
package main
import ("fmt")
func main(){
var ptr1 *int
var i int =1
ptr2 :=&i
if ptr1 == nil {
fmt.Println("prt1 是空指針")
}
if ptr2 != nil {
fmt.Println("prt2 不是空指針")
}
}
[root@localhost pointer]# go run pointer5.go
prt1 是空指針
prt2 不是空指針
5. Go 指針數組
定義了長度為 3 的整型數組:
package main
import "fmt"
const MAX int =3
func main(){
a :=[MAX]int{10,100,200}
var i int
for i =0; i < MAX; i++{
fmt.Printf("a[%d] = %d\n", i, a[i])}}
[root@localhost pointer]# go run pointer6.go
a[0] = 10
a[1] = 100
a[2] = 200
有一種情況,我們可能需要儲存數組,這樣我們就需要使用到指針。
以下聲明了整型指針數組:
var ptr [MAX]*int;
$ cat pointer7.go
package main
import "fmt"
const MAX int =3
func main(){
a :=[]int{9,99,999}
var i int
var ptr [MAX]*int
for i =0; i < MAX; i++{
ptr[i]=&a[i]/* 整數位址指派給指針數組 */
}
for i =0; i < MAX; i++{
fmt.Printf("a[%d] = %d\n", i,*ptr[i])
}
}
[root@localhost pointer]# go run pointer7.go
a[0] = 9
a[1] = 99
a[2] = 999