指针
Go 有指针。然而却没有指针运算,因此它们更像是引用而不是我们所知道的来自于 C的指针。指针非常有用。在 Go 中调用函数的时候,得记得变量是值传递的。因此,为了修改一个传递入函数的值的效率和可能性,有了指针。
通过类型作为前缀来定义一个指针’’:var p int。现在 p 是一个指向整数值的指针。所有新定义的变量都被赋值为其类型的零值,而指针也一样。一个新定义的或者没有任何指向的指针,有值 nil。在其他语言中,被叫做空(NULL)指针,在 Go 中就是 nil。让指针指向某些内容,可以使用取址操作符 (&),像这样:
var p *int
fmt.Printf("%v", p) // 打印 nil
var i int // 定义一个整形变量 i
p = &i // 使得 p 指向 i
fmt.Printf("%v", p) // 打印出来的内容类似 0x5ff72b76c000b
从指针获取值是通过在指针变量前置’*’ 实现的:
p = &i // 获取 i 的地址
*p = 8 // 修改 i 的值
fmt.Printf("%v\n", *p) // 打印 8
fmt.Printf("%v\n", i) // 同上
因为没有指针运算,所以如果这样在golang写:p++,它表示 (p)++:首先获取指针指向的值,然后对这个值加一,而不是对指针本身进行操作。
内存分配
Go 同样也有垃圾回收,也就是说无须担心内存分配和回收。
Go 有两个内存分配原语,new 和 make。它们应用于不同的类型,做不同的工作,可能
有些迷惑人,但是规则很简单。
用new分配内存
内建函数 new 本质上说跟其他语言中的同名函数功能一样:new(T) 分配了零值填充的 T 类型的内存空间,并且返回其地址,一个 *T 类型的值。用 Go 的术语说,它返回了一个指针,指向新分配的类型 T 的零值。
这意味着使用者可以用 new 创建一个数据结构的实例并且可以直接工作。 如bytes.Buffer 的文档所述 “Buffer 的零值是一个准备好了的空缓冲。” 类似的,sync.Mutex 也没有明确的构造函数或 Init 方法。取而代之,sync.Mutex 的零值被定义为非锁定的互斥量。
零值是非常有用的。例如这样的类型定义:
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
SyncedBuffer 的值在分配内存或定义之后立刻就可以使用。在这个片段中,p 和 v 都
可以在没有任何更进一步处理的情况下工作。
p := new(SyncedBuffer) // Type *SyncedBuffer,已经可以使用
var v SyncedBuffer // Type SyncedBuffer,同上
用 make 分配内存
回到内存分配。内建函数 make(T, args) 与 new(T) 有着不同的功能。它只能创建slice,map和 channel,并且返回一个有初始值(非零)的 T 类型,而不是 *T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个 slice,是一个包含指向数据(内部 array)的指针,长度和容量的三项描述符;在这些项目被初始化之前,slice 为 nil。对于 slice,map 和 channel,make 初始化了内部的数据结构,填充适当的值。
例如,make([]int, 10, 100) 分配了 100 个整数的数组,然后用长度 10 和容量 100创建了 slice 结构指向数组的前 10 个元素。区别是,new([]int) 返回指向新分配的内存的指针,而零值填充的 slice 结构是指向 nil 的 slice 值。
用一个例子展示new和make的不同:
var p *[]int = new([]int) // 分配 slice 结构内存;很少使用
var v []int = make([]int , 100) // v 指向一个新分配的有 100 个整数的数组
// 不必要的复杂例子
var p *[]int = new([]int)
*p = make([]int , 100, 100)
// 更常见
v := make([]int , 100)
make 仅适用于 map,slice 和 channel,并且返回的不是指针。应用 new 获得特定的指针。
new 分配;make 初始化
上面的两段可以简单总结为:
• new(T) 返回 *T 指向一个零值 T
• make(T) 返回初始化后的 T
当然 make 仅适用于 slice,map 和 channel。