指针

寒江蓑笠翁大约 6 分钟

指针

Go保留了指针,在一定程度上保证了性能,同时为了更好的GC和安全考虑,又限制了指针的使用。

创建

关于指针有两个常用的操作符,一个是取地址符&,另一个是解引用符*。对一个变量进行取地址,会返回对应类型的指针,例如:

func main() {
   num := 2
   p := &num
   fmt.Println(p)
}

指针存储的是变量num的地址

0xc00001c088

解引用符则有两个用途,第一个是访问指针所指向的元素,也就是解引用,例如

func main() {
	num := 2
	p := &num
	rawNum := *p
	fmt.Println(rawNum)
}

p是一个指针,对指针类型解引用就能访问到指针所指向的元素。还有一个用途就是声明一个指针,例如:

func main() {
   var numPtr *int
   fmt.Println(numPtr)
}
<nil>

*int即代表该变量的类型是一个int类型的指针,不过指针不能光声明,还得初始化,需要为其分配内存,否则就是一个空指针,无法正常使用。要么使用取地址符将其他变量的地址赋值给该指针,要么就使用内置函数new手动分配,例如:

func main() {
   var numPtr *int
   numPtr = new(int)
   fmt.Println(numPtr)
}

更多的是使用短变量

func main() {
   numPtr := new(int)
   fmt.Println(numPtr)
}

new函数只有一个参数那就是类型,并返回一个对应类型的指针,函数会为该指针分配内存,并且指针指向对应类型的零值,例如:

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++代码:

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中的数组也是如此,不过区别在于指针无法偏移,例如

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)

提示

标准库unsafe提供了许多用于低级编程的操作,其中就包括指针运算,前往标准库-unsafe了解细节。


new和make

在前面的几节已经很多次提到过内置函数newmake,两者有点类似,但也有不同,下面复习下。

func new(Type) *Type
  • 返回值是类型指针
  • 接收参数是类型
  • 专用于给指针分配内存空间
func make(t Type, size ...IntegerType) Type
  • 返回值是值,不是指针
  • 接收的第一个参数是类型,不定长参数根据传入类型的不同而不同
  • 专用于给切片,映射表,通道分配内存。

下面是一些例子:

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的通道