在 go 语言当中有两个用于内存分配的内建函数 new() 和 make() 。但是对于初学者而言, 它们之间的区别容易让人模糊。 简单总结之间的区别就一句话,new 只分配内存, make 用于 slice,map,和channel 的初始化。
一、new
1. 理解 new
在 C++ 当中 new 一片内存,将会调用构造函数进行初始化,但是在 go 中 new 一片内存只是将其设置为零值, 并且返回它的地址,一个类型为 *T 的值
go 语言当中没有 C++ 构造函数一类的说法,我们可以用 C 语言来描述 go 中 new 的过程:
T *t = (T*)malloc(sizeof(T));
memset(t, 0, sizeof(T));
但是上诉的描述并非完全正确, 在 go 语言当中零值对于不同的类型有不同的定义,例如 bool 类型的零值是 false, int 类型的零值是 0, 任意指针类型的零值是 nil ,string 类型的零值是空字符等。
pInt := new(int) // pInt, *int类型, 指向了一个匿名的 int 变量
fmt.Println(*pInt) // 0
*pInt = 3
fmt.Println(*pInt) // 3
2. 函数返回局部变量和new
然而 new 创建一个变量和普通变量声明语句创建变量没有什么区别,例如函数返回局部变量地址和返回一个 new 的匿名变量的地址一样,其变量的空间都不是栈区,而是在堆区,所以说 go 中的 new 函数从一定程度上来讲只是一个语法糖,不是一个新的基础概念。
下面的 两个newInt 函数有着相同的意义
func newInt() *int {
return new(int)
}
func newInt() *int{
var value int
return &value
}
通过上面我们可以得出一个结论:如果一个局部变量在函数返回后仍然被使用,这个变量会从heap,而不是stack中分配内存
3. new 大小为 0 的内存
在 go 语言中使用 new 的时候我们还需要注意一种情况就是 new 出来的类型都是空,也就是说类型的大小是0, 例如 new 一个 struct{} 或 [0]int 的类型,可能有相同的地址(依赖具体的语言实现)。(译注:请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看runtime.SetFinalizer函数相关文档)
二、make
1. 了解 slice
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice 的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。
2. 理解make
内建函数make(T, args) 和 new(T) 不一样。make 函数主要用来创建 slice,map,channel的对象,并且返回一个初始化的值(并非置为零值),返回值的类为T的值,而并非指向它的指针。
- slice(切片): size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容量;它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片
- map(映射):初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个小的起始大小。
- channel(通道):通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的(07 - 获取URL, 并发 goroutine和channel有通道类型的初步介绍)。
不同方式分配一个切片,长度为0,容量为10
var pArray *[]int = new([]int) // 分配了切片类型, *pArray = nil
var pSlice []int = make([]int, 0, 10) // 分配一个长度为0,容量为10的切片类型
// 这个方式是最鸡巴蛋疼的方式
var p *[]int = new([]int)
*p = make([]int, 0, 10)
// 编程中,最适合 go 的做法的make分配
p := make([]int, 0, 10)
总结
- 理解 new 和 make
- 关注函数返回局部变量的可行性
- new 一个大小为 0 的用法和注意事项
- make 使用的三种方式