略微加速

略速 - 互联网笔记

golang中make和new的区别

2023-08-09 leiting (710阅读)

标签 Golang

0. 先说结论

Go语言中new和make是内建的两个函数,主要用来创建分配类型内存。

  • new可以用于对任何类型的内存分配,并返回指向该内存的指针,且内存中存储的值为对应类型的零值。new不常用,一般不用它。

  • make函数只用于slice、map以及channel的内存分配和初始化(非零值)。make无可替代,我们在使用slice、map和channel的时候用make进行初始化。

理解new和make首先从Golang的零值开始。


1. 零值

1.1 何为零值

零值就是变量只有声明没有初始化时系统默认设置的值。以下变量即只有声明没有初始化值,所以会被默认赋值为其对应的零值。

var i int
var f float64
var b bool
var s string
var ip *int

1.2 零值表

类型零值
boolfalse
uint/uint8/uint16/uint32/uint640
int/int8/int16/int32/int640
float32/float640
complex64/complex1280+0i
uintptr0
byte0(对应空字符)
rune0
string""
struct内部属性全部是其对应0值
interfacenil
slicenil
mapnil
channil
funcnil
指针nil

2. 内置函数new

2.1 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函数:

  • 输入:只有一个参数Type,即分配的内存里存放的变量是什么类型

  • 输出:返回指向内存的指针,并且内存中存放的是该类型的零值

2.2 new函数Demo

func main() {
  ip := new(int)
  fmt.Println("指向int的指针:", ip) //0xc00001a098  
  fmt.Println("int的零值:", *ip)  //0  
  
  sp := new(string)  
  fmt.Println("指向string的指针:", sp) //0xc00004e260  
  fmt.Println("string的零值:", *sp)  //""  
  
  pp := new(Person)  
  fmt.Printf("指向person的指针: %p\n", pp)          //0xc0000503e0  
  fmt.Println("person.name的零值:", (*pp).name)   //""  
  fmt.Println("person.phone的零值:", (*pp).phone) //0  
  fmt.Println("person.id的零值:", (*pp).address)  //nil  
  
  ipp := new(*int)  
  fmt.Println("指向int指针的指针:", ipp) //0xc00000a030  
  fmt.Println("int指针的零值:", *ipp)  //nil  
  
  mp := new(map[string]string)  
  fmt.Printf("指向map的指针: %p\n", mp)               //0xc0000ca028  
  fmt.Printf("指向map的指针是否为nil: %t", (*mp) == nil) //true
}
type Person struct {
  name    string  
  phone   int  
  address *string
}

看下面的这张图便一目了然。

其实,无论是map、slice还是channel其底层都是一个指针,map是一个指向hmap的指针,channel是一个指向hchan的指针,因为创建map、slice、channel时,底层分别返回的是*hmap、*reflect.SliceHeader、*hchan。其中*hchan在我的另一篇文章没名儿:Golang分享(一):channel底层原理中可以得到证明。所以我不建议大家去理解引用,也不用再纠结Go引用和指针的区别了,就是一个语法糖而已。

对上述代码我们在main函数最后新增三行初始化map。

(*m) = map[string]string{"张三丰": "武当山", "乔峰": "少林寺"}
  fmt.Printf("%p\n", (*m))  //0xc00007e4b0  
  fmt.Printf("%p\n", &(*m)) //0xc0000ca028

同样,看下面的这张图便一目了然。


3. 内置函数make

3.1 make的源码

// 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.
func make(t Type, size ...IntegerType) Type

如源码的英文注释所示,make函数:

  • 输入:一个仅限channel、map、slice的参数Type。第二个参数用于指定长度length,第三个参数用于指定容量capacity

  • 输出:一个指定长度和容量的channel、map或者slice

make也是用于内存分配的,但是和new不同,它只用于channel、map以及slice的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型。

3.2 make函数的Demo

func main() {
  mapDemo := make(map[string]string)  
  fmt.Println("mapDemo: ", mapDemo) //demo:  map[]  
  
  chanDemo := make(chan int, 10)  
  fmt.Println("chanDemo: ", chanDemo) //chanDemo:  0xc000116000  
  
  sliceDemo := make([]int, 10)  
  fmt.Println("sliceDemo: ", sliceDemo) //sliceDemo:  [0 0 0 0 0 0 0 0 0 0]
}

4.关于零值和初始化的理解

func main() {
  //先看make的初始化  
  mp1 := make(map[string]string)  
  mp1["乔峰"] = "少林寺"  
  fmt.Println(mp1)  
  
  //再看new的置零值  
  mp := new(map[string]string)  
  fmt.Printf("指向map的指针是否为nil: %t\n", (*mp) == nil) //true  
  //以下这行会报错,因为*mp被置为零值nil,没有初始化,  
  (*mp)["张三丰"] = "武当山"
}

new函数:返回指向map的指针,而map是零值nil,故而无法进行set操作

make函数:返回map(map本质就是指向hmap的指针),而map已经被初始化(非零值),故而可以直接进行set操作


https://www.zhihu.com/question/446317882/answer/3155926299

北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3