简介
var a int32 = 10
复制代码
如上,a
就是一个变量,那么我们为什么需要变量呢?因为在编程中,我们要操作的数据存放在内存中,如果直接操作内存地址,可读性非常差,所以需要用标识符来表示相应的内存地址,也就是变量,通过操作变量来操作其对应的内存。
简单来说,指针就是变量对应的内存地址。不是每个值都有内存地址,但每个变量必然有对应的内存地址。通过指针,我们可以直接操作变量的值,而不需要知道变量的名字。
可以将指针理解为内存地址,但严格上讲并不准确。应该这么说:对于变量
a
,a
的指针保存了a
的内存地址;对于整型int
,其指针类型*int
是指向int
类型的指针。
&
和 *
取址符 &
用来获取变量的内存地址;而 *
作用在指针上时是获取该指针指向的值,出现在类型前面时表示的是该类型的指针类型。举例:
a := 10 // 10
p := &a // 0xc00000a0a8
fmt.Println(*p) // 10
var p2 *int
fmt.Printf("%p %v", p2, p2) // 0x0 <nil>
p2 = &a // 0xc00000a0a8
*p2 = 20 // 0xc00000a0a8
fmt.Println(a) // 20,改变了原来的值,详细请见下面的章节
// 对于变量 var,var == *(&var) 是正确的
b := 1
fmt.Println(b == *(&b)) // true
复制代码
上面也可以看出,指针类型的零值是 nil
。
另外,var ptr*int
也是有效的(ptr
和 *
之间没有空格),但请尽量不要用这种语法,容易被误认为是乘法表达式。
指针的格式化标识符为
%p
。
深入
图解例子:
var a int = 1
ptr := &a
fmt.Println(a) // 1
fmt.Println(ptr) // 0xc420018100
复制代码
简单理解,变量 a
的值 1
存储在内存块 0xc420018100
,指针 ptr
直接指向内存地址,当我们操作 ptr
时,a
就自然变化了:
*ptr = 3
fmt.Println(a) // 3
复制代码
比较
- 任何类型的指针的零值都是
nil
- 不同的指针类型不能比较,即便都是
nil
- 指向同一个变量或都是
nil
时相等
a := 1
b := 2
s := "abc"
var ptr1 *int
var ptr2 *int
var ptr3 *string
&a == &s // invalid operation: &a == &s (mismatched types *int and *string)
&a == &b // false
ptr1 == ptr2 // true
ptr1 == ptr3 // invalid operation: ptr1 == ptr3 (mismatched types *int and *string)
ptr1 == &a // false
ptr1 = &a
ptr1 == &a // true
复制代码
注意
不能对常量或文字取址:
&10 // cannot take the address of 10
&"abc" // cannot take the address of "abc"
复制代码
Go
中的指针是内存安全的,更类似于 Java
、C#
等中的引用或 Python
中的可变类型。
应用
传递一个变量的引用(即传递指针)的性能是非常高的,只占用了几个字节,这在函数的参数传递中很有用处。当程序中需要占大量的内存,或者有很多的变量,使用指针能有效的提高效率,请见后续的函数中的“引用传递”。