Pointer in Go
变量是一块存放了值的存储空间,通过变量名来标识。
指针的值是变量的地址。
指针存放的是变量的值所在的存储空间的地址。
不是每个值都有地址,但每个变量都有地址。
通过指针可以在不知道变量名(如果有变量名的话)的情况下间接更新变量的值。
x := 1
p := &x // p, of type *int (pronounced "pointer to int"); we say "p points to x"
*p = 2 // *p denotes a variable and yields the value of x
聚合类型的每个元素,如结构体的字段和数组的元素,也是一个变量,因此也有地址。
变量也被称为可寻址的值。取地址操作符 &
只可用在表示变量的表达式上。
任何类型的指针的零值都是 nil
,若 p
指向一个变量则 p != nil
为真。
指针之间可以比较。两个指针指向同一个变量或是都为 nil
,则视为相等。
函数可以返回内部局部变量的地址。
func f() *int {
v := 1
return &v
}
var p = f() // 局部变量 v 在函数返回后仍存在,因为 p 指向它
// 每次调用都返回不同的地址
fmt.Println(f() == f()) // "false"
由于指针包含了变量的地址,将指针作为函数参数传入,函数内部可以更新这个通过指针间接传入的变量。
每个取地址或拷贝指针的操作,实际上都为变量创建了一个别名。除此之外,拷贝其它引用类型的值比如切片、map、通道,拷贝包含引用类型的结构体、数组和接口也会创建别名。
指针别名可以让我们在不知道变量名的情况下访问该变量,它的弊端是为了找到访问某个变量的所有语句,我们必须知道变量的所有别名。
指针使用示例:
// Usage message will be printed if the user provides an invalid argument,
// an invalid flag , or -h or -help.
var n = flag.Bool("n", false, "omit trailing newline") // *bool
var sep = flag.String("s", " ", "separator") // *string
func main() {
flag.Parse() // update the flag variables from their default values
fmt.Print(strings.Join(flag.Args(), *sep)) // flag.Args returns the non-flag command-line arguments.
if !*n {
fmt.Println()
}
}
// go run main.go -n -s - a b c
// a-b-c
new
函数也可以创建变量。new(T)
创建类型为 T
的不具名变量,将其初始化为类型 T
,并返回该变量的地址(*T
类型)。
new
只是一个语法糖,创建的变量和普通的变量无异,但是无需取变量名,而且 new(T)
可直接用于表达式中。
func newInt() *int {
return new(int)
}
func newInt() *int {
var dummy int
return &dummy
}
每次调用 new
都会返回地址不同的变量。存在一种例外情况就是,不携带信息、大小为零的类型,如 struct{}
和 [0]int
,根据不同的具体实现,两个这种类型的变量拥有相同的地址。
很少会用到 new
函数。最常用的不具名变量是结构体类型,这种情况下也是使用结构体字面量语法更为灵活。
new
是预定义的函数,不是关键字,可以被重载。
// delta 函数里自带的 new 函数不可用
func delta(old, new int) int { return new - old }