一、前言
make 和 new 是 Go 语言中的两个内建函数,主要用来分配指定类型的内存空间。
二、变量的声明
请看下面的两个变量声明代码
var i int // 0
var s string // ""
var j *int // nil
当我们不指定变量的默认值时,这些变量的默认值是对应的零值;
需要注意的是:引用类型的零值是 nil,它表示还未为该变量分配内存空间,无法寻址
func main() {
var j *int
*j = 10
fmt.Println(*j)
}
运行上述程序,触发 panic:
panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4849df]
从 panic 信息可以看出,对于引用类型的变量,我们不光要声明它,如果我们要想使用它,必须对其进行初始化(分配内存空间),否则我们将值存到哪里?而对于值类型的声明来说就不需要了,因为已经默认帮我们分配好了。
三、new
我们使用 new 为刚才的引用变量分配内存空间,看看是否能够正常运行:
func main() {
var j *int
j = new(int)
*j = 10
fmt.Println(*j)
}
现在程序可以完美运行,打印出 10,接着我们看下内置的 new 函数都是做了些什么工作。
// 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.
func new(Type) *Type
翻译成人话:new 内置函数用于分配内存。第一个参数是类型,而不是值,返回的值是指向该类型的新分配的零值的指针。
它只接受一个参数,这个参数是一个类型,分配好内存后,返回一个指向该类型内存地址的指针。同时请注意:它同时把分配的内存置为了该类型的零值。
所以,在上面代码中,如果没有 *j = 10 ,那么也会打印出 0;这个例子还不能提现 new 这种内置函数的优点,看下面的示例:
type user struct {
lock sync.Mutex
name string
age int
}
func main() {
u := new(user) // 默认给 u 分配 user 全部字段的零值
u.lock.Lock() // 可以直接使用
u.name = "abc"
u.lock.Unlock()
fmt.Println(u)
}
运行后打印如下信息:
&{{0 0} 张三 0}
示例中的 user 类型中的 lock 字段不需要进行初始化了,直接可以用,不会出现无效内存引用异常,因为它已经被赋予零值了。
sync.Mutex 类型是一种值类型,在声明后就可以直接使用,无需初始化
这就是 new 内建函数,它返回的永远是指向某种类型的指针,指针指向分配指定类型的内存地址。
四、make
make 内建函数也是用来做内存分配的,但是它和 new 不同,它只用于一些特定类型的内存分配,这些类型都是 Go 中的核心类型,包括通道(chan)、字典(map)和切片(slice)。而且它返回的是这些类型本身,因为这三种类型底层类型就是引用类型,所以没有必要再返回他们的指针了。
也正是因为这三种类型是引用类型,所以再使用之前必须进行初始化,但是是否置为零值就和 new 有一点区别了。
func make(t Type, size ...IntegerType) Type
从函数声明中也可以看到,返回的是类型本身。
五、make 和 new 的异同
相同点
- 均在堆空间分配内存
不同点
- make 只用于 slice、map 和 channel 的初始化,返回的是这几种引用类型本身;
- new 用于给指定类型分配一块内存空间,同时置对应类型为它的零值,然后返回指向指定类型的指针;
- new 用于指定类型内存的分配(初始化为零值),不常用。
在实际编码中,我们往往会使用短变量声明和结构体字面量的方式达到初始化的目的,比如 i := 0 或者 u := user{};