变量是一块存放了值的存储空间,通过变量名来标识。

指针的值是变量的地址。

指针存放的是变量的值所在的存储空间的地址。

不是每个值都有地址,但每个变量都有地址。

通过指针可以在不知道变量名(如果有变量名的话)的情况下间接更新变量的值。

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 }