Go指針
Go 保留了指針,在一定程度上保證了性能,同時為了更好的 GC 和安全考慮,又限制了指針的使用。
創建
關於指針有兩個常用的操作符,一個是取地址符&,另一個是解引用符*。對一個變量進行取地址,會返回對應類型的指針,例如:
go
func main() {
num := 2
p := &num
fmt.Println(p)
}指針存儲的是變量num的地址
0xc00001c088解引用符則有兩個用途,第一個是訪問指針所指向的元素,也就是解引用,例如
go
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p是一個指針,對指針類型解引用就能訪問到指針所指向的元素。還有一個用途就是聲明一個指針,例如:
go
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int即代表該變量的類型是一個int類型的指針,不過指針不能光聲明,還得初始化,需要為其分配內存,否則就是一個空指針,無法正常使用。要麼使用取地址符將其他變量的地址賦值給該指針,要麼就使用內置函數new手動分配,例如:
go
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}更多的是使用短變量
go
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}new函數只有一個參數那就是類型,並返回一個對應類型的指針,函數會為該指針分配內存,並且指針指向對應類型的零值,例如:
go
func main() {
fmt.Println(*new(string))
fmt.Println(*new(int))
fmt.Println(*new([5]int))
fmt.Println(*new([]float64))
}
0
[0 0 0 0 0]
[]禁止指針運算
在 Go 中是不支持指針運算的,也就是說指針無法偏移,先來看一段 C++代碼:
cpp
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[0];
cout << &arr << endl
<< p << endl
<< p + 1 << endl
<< &arr[1] << endl;
}0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884可以看出數組的地址與數字第一個元素的地址一致,並且對指針加一運算後,其指向的元素為數組第二個元素。Go 中的數組也是如此,不過區別在於指針無法偏移,例如
go
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// 試圖進行指針運算
p++
fmt.Println(p)
}這樣的程序將無法通過編譯,報錯如下
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)TIP
標准庫unsafe提供了許多用於低級編程的操作,其中就包括指針運算,前往標准庫-unsafe了解細節。
new 和 make
在前面的幾節已經很多次提到過內置函數new和make,兩者有點類似,但也有不同,下面復習下。
go
func new(Type) *Type- 返回值是類型指針
- 接收參數是類型
- 專用於給指針分配內存空間
go
func make(t Type, size ...IntegerType) Type- 返回值是值,不是指針
- 接收的第一個參數是類型,不定長參數根據傳入類型的不同而不同
- 專用於給切片,映射表,通道分配內存。
下面是一些例子:
go
new(int) // int指針
new(string) // string指針
new([]int) // 整型切片指針
make([]int, 10, 100) // 長度為10,容量100的整型切片
make(map[string]int, 10) // 容量為10的映射表
make(chan int, 10) // 緩沖區大小為10的通道