面试官:Golang 的 new 与make 区别是什么?

7,505 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

本篇文章基于 Golang 1.17.2

胖虎坐在公司工位上正在吃刚才买的包子。

新来的小学妹就匆忙地跑过来,慌慌张张说:“学长,不好了,线上代码出现问题了。”

胖虎赶紧放下包子,来不及擦嘴,迅速掏出电脑,边打开电脑边问“你知道哪里报错吗,为什么报错吗”

学妹:“不知道啊……”

胖虎:“……行吧,我自己看下吧。”

img

学妹从来没有见过如此严肃的学长,大气也不敢喘,也不敢走,就默默的看学长在查问题。

胖虎:“找到了,这是谁写的代码啊,map 使用了 new 初始化”

map1 := new(map[string]string)

学妹:“是我,学长……” 胖虎:“行吧,看在你是我学妹份上,今天跟你简单科普一下吧”

img

变量声明的方式

 var test1 int

我们可以通过 var+变量名称+变量类型 进行声明变量,当我们没有给它赋值的时候,它们的结果是变量类型的零值。

比如说 string 的零值是"", int 的零值是0,引用类型的零值是nil。

以上两种类型我们可以直接使用,但如果把它改成指针会怎么样呢?

package main

import "fmt"

func main() {
  var test *string
  fmt.Println(test)
  *test = "测试"
}

执行结果如下:

img

这是为什么呢,因为对于引用类型的变量,不仅要声明,并且还要给它分配内存。怎么给它分配内存呢?这就要用到了new了

什么是new

new 是 Golang 的内置函数,源代码如下:

img

大意是,分配内存的内置函数,第一个参数是类型,而不是具体的值,返回值是该类型的指针。分配的值是该类型零值的指针。

“我知道怎么改了” 学妹兴奋的说道,说完便在编辑器加了两行代码。

package main

import "fmt"

func main() {
  var test *string
  fmt.Println(test)
  test = new(string)
  *test = "测试"
  fmt.Println(*test)
}

胖虎:“恩,不错,学得挺快的嘛,那我再问你一下,复合类型的slice、map、chan使用 new 后可以使用吗?为什么呢”

学妹:“这你刚才没说啊,我不知道”

胖虎:“咱们可以敲下代码,演示一下嘛”

package main

import "fmt"

func main() {
  testMap := new(map[string]string)
  (*testMap)["aa"] = "aa"
  fmt.Println(testMap)
}

执行下代码,竟然报错了,“这是为什么呢?”

img

胖虎:“真相只有一个,那就是,map 底层是结构体,这样说,你可能不理解,举个🌰吧。”

Talk is cheap. Show me the code

package main

import "fmt"

type XueMei struct {
  age          *int64 `json:"age"`
  BoyFiriendYn bool   `json:"boy_firiend_yn"`
}

func main() {
  test := new(XueMei)
  //是否有男朋友
  test.BoyFiriendYn = false

  //此处代码会导致panic
  //panic: runtime error: invalid memory address or nil pointer dereference
  //[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x108a0e9]
  *test.age = 1
}

也就是说它里面的成员变量仍未进行初始化,所以它们几个初始化要使用make来进行。

学妹崇拜的眼光“ 学长你懂得真多,你还能说说什么是 make 吗?”

什么是make

make 也是用于内存分配的内置函数,但是和new不同,源码如下图所示。

img

大意是make内置函数分配并初始化一个slice、map或chan类型的对象。像new函数一样,第一个参数是类型,而不是值。

与new不同,make的返回类型与其参数的类型相同,而不是指向它的指针。结果的取决于传入的类型。

并且 slice在 make 的时候,第二个参数必须传递,也就是切片的长度。否则会编译失败。

new函数底层实现

new函数底层主要是调用go1.17/src/runtime/malloc.go中的 newobject 方法。

img

编辑切换为居中

添加图片注释,不超过 140 字(可选)

这里可以看到 newobject 方法,底层调用 mallocgc 方法的时候,needzero 传的是 true ,所以返回值是传入类型零值的指针。

make函数底层实现

通过执行以下命令go tool compile -N -l -S file.go

我们可以看到make函数初始化

slice调用的是runtime.makeslice、runtime.makeslice64这两个方法.

func makeslice(typ *byte, len int, cap int) unsafe.Pointer
func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer

编辑切换为居中

添加图片注释,不超过 140 字(可选)

map调用的是runtime.makemap64、runtime.makemap、runtime.makemap_small这三个方法.

func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any)
func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
func makemap_small() (hmap map[any]any)

chan分别调用的是runtime.makechan64、runtime.makechan这三个方法.

func makechan64(chanType *byte, size int64) (hchan chan any)
func makechan(chanType *byte, size int) (hchan chan any)

感兴趣的同学可以去看下源代码

总结

学妹:懂了学长,那我总结一下吧

image.png

胖虎:“恩,不错不错,果然没有看错你,今天晚上有时间吗,跟大家分享一下你今天学到的知识吧。”

学妹:“改天可以吗?晚上男朋友约我一起吃饭了”

胖虎os:当初面试时候她说没有男朋友,才把她招进来的,怎么现在突然有男朋友了?没想到小丑竟然是我自己

img

更多优质的技术文章欢迎关注公号【后端时光

如果对你有帮助,帮忙点个赞。谢谢