ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 使用new进行分配 使用new进行分配 Go有两个分配原语,内建函数new和make。它们所做的事情有所不同,并且用于不同的类型。这会有些令人混淆,但规则其实很简单。我们先讲下new。这是一个用来分配内存的内建函数,但是不像在其它语言中,它并不初始化内存,只是将其置零。也就是说,new(T)会为T类型的新项目,分配被置零的存储,并且返回它的地址,一个类型为*T的值。在Go的术语中,其返回一个指向新分配的类型为T,值为零的指针。 由于new返回的内存是被置零的,这会有助于你将数据结构设计成,每个类型的零值都可以使用,而不需要进一步初始化。这意味着,数据结构的用户可以使用new来创建数据,并正确使用。例如,bytes.Buffer的文档说道,"Buffer的零值是一个可以使用的空缓冲"。类似的,sync.Mutex没有显式的构造器和Init方法。相反的,sync.Mutex的零值被定义为一个未加锁的互斥。 “零值可用”的属性是可以传递的。考虑这个类型声明。 ``` type SyncedBuffer struct { lock sync.Mutex buffer bytes.Buffer } ``` SyncedBuffer类型的值也可以在分配或者声明之后直接使用。在下一个片段中,p和v都不需要进一步的处理便可以正确地工作。 ``` p := new(SyncedBuffer) // type *SyncedBuffer var v SyncedBuffer // type SyncedBuffer ``` ## 构造器和复合文字 有时候零值并不够好,需要一个初始化构造器(constructor),正如这个源自程序包os的例子。 ```go func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := new(File) f.fd = fd f.name = name f.dirinfo = nil f.nepipe = 0 return f } ``` 有许多这样的模版。我们可以使用*复合文字(composite literal)*进行简化,其为一个表达式,在每次求值的时候会创建一个新实例。 ```go func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := File{fd, name, nil, 0} return &f } ``` 注意,不像C,返回一个局部变量的地址是绝对没有问题的;变量关联的存储在函数返回之后依然存在。实际上,使用复合文字的地址也会在每次求值时分配一个新的实例,所以,我们可以将最后两行合并起来。 return &File{fd, name, nil, 0} 复合文字的域按顺序排列,并且必须都存在。然而,通过field:value显式地为元素添加标号,则初始化可以按任何顺序出现,没有出现的则对应为零值。因此,我们可以写成 return &File{fd: fd, name: name} 作为一种极端情况,如果复合文字根本不包含域,则会为该类型创建一个零值。表达式new(File)和&File{}是等价的。 复合文字还可用于arrays,slices和maps,域标号使用适当的索引或者map key。下面的例子中,不管Enone,Eio和Einval的值是什么,只要它们不同,初始化就可以工作。 a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"} ##使用make进行分配 回到分配的话题。内建函数make(T,args)与new(T)的用途不一样。它只用来创建slice,map和channel,并且返回一个初始化的(而不是置零),类型为T的值(而不是*T)。之所以有所不同,是因为这三个类型的背后是象征着,对使用前必须初始化的数据结构的引用。例如,slice是一个三项描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是nil的。对于slice,map和channel,make初始化内部数据结构,并准备好可用的值。例如, make([]int, 10, 100) 分配一个有100个int的数组,然后创建一个长度为10,容量为100的slice结构,并指向数组前10个元素上。(当创建slice时,容量可以省略掉,更多信息参见slice章节。)对应的,new([]int)返回一个指向新分配的,被置零的slice结构体的指针,即指向nilslice值的指针。 这些例子阐释了new和make之间的差别。 ``` var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints // Unnecessarily complex: var p *[]int = new([]int) *p = make([]int, 100, 100) // Idiomatic: v := make([]int, 100) 记住make只用于map,slice和channel,并且不返回指针。要获得一个显式的指针,使用new进行分配,或者显式地使用一个变量的地址。 ```