Go语言中的make和new

Go语言中的make和new

May 28, 2023
后端开发, Go
Go, Golang

关于Go语言中make和new的区别,已经在网上看到了很多文档,但是总觉得缺点什么,所以今天就自己写一篇文章来讲一下。

首先先说下网上说的关于make和new的区别,大致有以下几点:

  • make只能用于slice、map、channel的初始化,返回的是这三个类型本身。
  • new用于为任何类型分配内存,并返回指向该类型的指针。
  • new和make都能用于分配内存,但是make会初始化内存,而new不会。

make #

make用于创建slice、map、channel,返回的是这三个类型本身。

有一个很普遍的说法:slice、map、channel是引用类型。也有人说,go语言中没有引用类型 。我们今天不争论这个,因为这只是概念上的问题,争论这个意义不大。

不过,Go官方的代码提交记录中,的确是在2013年就删除了对引用类型的支持。 而之所以能很多人还在说slice、map、channel、function、interface是引用类型,是因为这几种类型的实现中,有指针指向底层数据,比如slice, 在slice的实现中,有指针指向底层数组:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

如果用这个标准来判断,那么string也是引用类型,因为string的实现中,也有指针指向底层数据:

type StringHeader struct {
	Data uintptr
	Len  int
}

slice, map, channel的区别 #

来看一个有趣的例子:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	arr := make([]int, 4, 8)
	maps := make(map[string]string, 10)
	chans := make(chan int, 10)

	arr[0] = 1
	arr[1] = 2

	maps["a"] = "a"

	chans <- 1

	fmt.Println("sizeof slice:", unsafe.Sizeof(arr))
	fmt.Println("sizeof map:", unsafe.Sizeof(maps))
	fmt.Println("sizeof chan:", unsafe.Sizeof(chans))
}

运行结果: sizeof 可以看到,slice的大小是24字节,这个很好理解,因为slice的实现中,有指针指向底层数组,所以slice的大小是指针的大小(8字节)+ len(8字节)+ cap(8字节)。 而为什么map和chan的大小是8字节呢?是因为其底层结构体的大小都是8字节? 不是,map的底层对应的结构体是hmap,而chan的底层对应的结构体是hchan,其大小都远远超过了8字节:

那为什么make出来的chan和map是8字节呢?而8字节,正好是指针的大小。 我们来看一下示例代码的汇编:

go build -gcflags -S ./main.go

运行结果: 汇编 可以看到,使用make创建map和chan时,分别调用了runtime.makemap和runtime.makechan,这两个函数的返回值都是指针,所以make出来的map和chan的大小都是8字节。

slice和map用作函数参数时的区别 #

下面来看两个小例子:

在go语言中,只有值传递,没有引用传递。使用slice和map作为函数参数时,传递的是slice和map的拷贝。而通过上面的讲解我们知道:slice本身是结构体,而 map本身是指针。 所以,当我们传递slice时,是把slice成员变量的值赋给了形参(也就是slice的拷贝),包含底层数组指针、长度值、容量值,如果我们在函数中 修改了slice的成员变量,那实际上是修改了slice的拷贝,而不是原来的slice。只不过,如果slice没有发生扩容,那么slice底层还是指向原来的数组,所以在 函数内部修改slice某个元素的值,会影响到原来的slice。但是,如果slice发生了扩容,那么slice底层指向的就是一个新的数组,所以在函数内部修改slice某 个元素的值,不会影响到原来的slice。 而当传递map时,是把hmap结构体的指针赋给了形参,所以在函数内部修改map,会影响到原来的map。

new #

new用于为任何类型分配内存,并返回指向该类型的指针。 使用new来创建map、slice和channel时,并不会初始化内部的数据结构。

  • 对于slice,返回的是一个指向底层结构体的指针,该结构体的成员变量都是零值。
  • 对于map和chan来说,返回的是一个指向指针的指针,由于没有初始化, 其指向的就是一个nil指针。
new创建的map和chan是无法直接使用的。