前言
在Go语言中,初始化数据结构的时候,可能会用到2个内置函数:new和make。
new和make都可以用来分配内存,那他们有什么区别呢?在写代码过程中,对于new和make的最佳实践又是什么呢?
new是什么
我们先看看new的官方定义:
func new(Type) *Type
The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.
从官方定义里可以看到,new有以下几个特点:
- 分配内存。内存里存的值是对应类型的零值。
- 只有一个参数。参数是分配的内存空间所存储的数据类型,Go语言里的任何类型都可以是new的参数,比如int, 数组,结构体,甚至函数类型都可以。
- 返回的是指针。
以下代码是等价的,可以认为new(T)是 var t T; &t 的语法糖。
注意:Go里的new和C++的new是不一样的:
- Go的new分配的内存可能在栈(stack)上,可能在堆(heap)上。C++ new分配的内存一定在堆上。
- Go的new分配的内存里的值是对应类型的零值,不能显示初始化指定要分配的值。C++ new分配内存的时候可以显示指定要存储的值。
- Go里没有构造函数,Go的new不会去调用构造函数。C++的new是会调用对应类型的构造函数。
make是什么
我们再看看make的官方定义:
func make(t Type, size ...IntegerType) Type
The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type:
Slice: The size specifies the length. The capacity of the slice is equal to its length. A second integer argument may be provided to specify a different capacity; it must be no smaller than the length. For example, make([]int, 0, 10) allocates an underlying array of size 10 and returns a slice of length 0 and capacity 10 that is backed by this underlying array.
Map: An empty map is allocated with enough space to hold the specified number of elements. The size may be omitted, in which case a small starting size is allocated.
Channel: The channel's buffer is initialized with the specified buffer capacity. If zero, or the size is omitted, the channel is unbuffered.
从官方定义里可以看到,make有如下几个特点:
- 分配和初始化内存。
- 只能用于slice, map和chan这3个类型,不能用于其它类型。
如果是用于slice类型,make函数的第2个参数表示slice的长度,这个参数必须给值。
- 返回的是原始类型,也就是slice, map和chan,不是返回指向slice, map和chan的指针。
几个问题
这里来回答几个使用new和make过程中的常见问题,从简单到复杂:
- 为什么针对slice, map和chan类型专门定义一个make函数?
答案:这是因为slice, map和chan的底层结构上要求在使用slice,map和chan的时候必须初始化,如果不初始化,那slice,map和chan的值就是零值,也就是nil。我们知道:
- map如果是nil,是不能往map插入元素的,插入元素会引发panic
- chan如果是nil,往chan发送数据或者从chan接收数据都会阻塞
- slice会有点特殊,理论上slice如果是nil,也是没法用的。但是append函数处理了nil slice的情况,可以调用append函数对nil slice做扩容。但是我们使用slice,总是会希望可以自定义长度或者容量,这个时候就需要用到make。
- 可以用new来创建slice, map和chan么?
答案:可以。代码可以参考如下示例:
输出结果是:
虽然new可以用来创建slice, map和chan,但实际上并没有卵用,因为new创建的slice, map和chan的值都是零值,也就是nil。这3种类型如果是nil,那遇到的问题我们在上面第一个问题已经解答过了,这里不再赘述。
- 为什么slice是nil也可以直接append?
答案:对于nil slice,append会对slice的底层数组做扩容,通过调用mallocgc向Go的内存管理器申请内存空间,再赋值给原来的nil slice。
- slice用make创建的时候,如果指定的长度len>0,则make创建的slice下标索引从0到len-1的值都是对应slice里元素类型的零值。参考下例:
最佳实践
- 尽量不使用new
- 对于slice, map和chan的定义和初始化,优先考虑使用make函数
开源地址
本文章和代码开源在:https://github.com/jincheng9/go-tutorial,也欢迎大家关注公众号:coding进阶,学习更多Go知识。