越来越多的开发者开始使用Go
语言开发,这其中不乏有PHP
、Python
、Java
的,还有C
和C++
转Go
的,技术功底可谓是鱼龙混杂、良莠不齐。无论是从小白入门还是直接上手做项目,如果要真正掌握一门编程语言,基本功
早晚都要练的。
言归正传,在Go
语言中有两个用来内存分配的内置函数:new
和make
。经历了很多项目,发现make
的使用频率要远高于new
,实际开发中make
几乎是无可代替,之所以new
的使用频率没那么高,是因为它有几种可以代替的编写方式。虽然都用于内存分配,但它们还是有一些区别,所以应用场景也各不相同。
NEW
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
该函数的第一个参数Type
是一个类型,不是一个值,它返回被分配了零值
这个类型的指针。零值
是类型的默认值,例如:int
的零值是0
,string
的零值是""
,指针、切片、映射、通道和函数等这些引用类型的零值
是nil
。
package main
import (
"fmt"
)
func main() {
s := new(string)
fmt.Println(s) // output: 0xc00012a0b0
fmt.Println(*s) // output:
}
可以看出,new(string)
分配了一个字符串类型的指针,打印出的0xc00012a0b0
为指针地址,其地址上的值为空字符串
。
package main
import (
"fmt"
)
type User struct {
Name string
Age int
}
func main() {
var u *User
u = new(User)
u.Name = "alice"
u.Age = 18
fmt.Println(u) // output: &{alice 18}
fmt.Println(*u) // output: {alice 18}
fmt.Println(&u) // 0xc00005a040
}
上面这个例子,new
了一个User
类型,返回的是User
的指针。因为u
是指针类型,如果不分配内存初始化它,它是nil
,无法用来赋值并使用,所以通过new
来分配内存以后才能赋值使用。我在开头说了new
的使用频率并不多,那就是因为它还有一种代替的写法:
package main
type User struct {
Name string
Age int
}
func main() {
var u *User
u = &User{}
}
那new(User)
和&User{}
有什么区别呢?对于日常开发来说几乎无区别,并且后者更常用和简洁高效,也能在初始化时给元素直接赋值。如果不考虑初始赋值的情况下,前者在性能上更有优势,日常开发可无须过渡关注两者区别。
MAKE
再来说说make
函数,make
仅用于特定类型的内存分配和初始化,那就是只能用于slice
、map
、channel
这三个特定类型。使用make
初始化时,可以指定长度,如果初始化slice
,还可以指定切片的初始容量。除此之外,make
返回的是类型本身,并不是指针。
package main
import "fmt"
func main() {
s := make([]int, 4, 16) // size: 4, cap: 16
fmt.Println(s) // output [0 0 0 0]
m := make(map[string]int, 4) // size: 4
m["k0"] = 1
fmt.Println(m) // output: map[key0:1]
ch := make(chan int, 1) // size: 1
ch <- 10
fmt.Println(<-ch) // output: 10
}
前面说了,new
也可以为指定类型分配内存,那如果用new
来为slice
map
channel
分配内存会怎样呢?
package main
import "fmt"
func main() {
m := new(map[string]string) // m 的类型为 *map[string]string
fmt.Println(&m) // 0xc00005a040
fmt.Println(*m == nil) // true
(*m)["k0"] = "v0" // panic: assignment to entry in nil map
}
这里new
为map[string]string
分配了内存地址,返回指向该内存地址的指针,但指针指向的地址上的值依然为nil
,就是说new
并没有为map
分配初始结构。
package main
import "fmt"
func main() {
a := *new([]int)
fmt.Printf("%v, %t\n", a, a == nil) // output: [], true
b := *new(map[string]int)
fmt.Printf("%v, %t\n", b, b == nil) // output: map[], true
c := *new(chan int)
fmt.Printf("%v, %t\n", c, c == nil) // output: <nil>, true
}
由此看出,无论是slice
、map
还是channel
,new
出来的指针指向的地址上的值都为nil
,所以并不能直接使用,这就是make
的大有作为。当然在某些条件下,make
也是可以替代的,我们可以直接初始化并且还可以赋值:
package main
import "fmt"
func main() {
s := []int{}
fmt.Println(s)
m := map[string]string{}
fmt.Println(m)
}
需要注意的是chan
类型必须通过make
初始化,若仅用var
声明而不使用make
,通道(chan)的默认零值是nil
。
最后
简而言之,new
用来分配内存,返回指针,适用于基本类型
、结构体
指针类型的创建。
而make
是专用于slice
、map
和channel
这些引用类型的初始化,可以指定长度和容量,返回一个直接可用的结构。