一句话概括:
new(T):分配一块零值内存,返回 *T,适用于任意类型。
make(T, args...):初始化 slice、map、channel 的内部数据结构,返回 T 本身,只适用于这三种引用类型。
内建函数签名与语义
两者都是 Go 的内建函数,不需要 import,也不能被当作普通函数值使用。
new 的语义
new(T) 做两件事:
- 分配一块足够存放
T 的内存;
- 把这块内存清零,使其成为
T 的零值;
- 返回指向它的
*T。
p := new(int) fmt.Println(*p) *p = 10 fmt.Println(*p)
|
make 的语义
make(T, args...) 不是简单分配零值内存,而是初始化内部结构:
slice:分配底层数组,设置 len、cap,返回 slice header;
map:初始化哈希表,返回可读写的 map;
chan:初始化 channel 的缓冲区、锁、等待队列等。
s := make([]int, 3) fmt.Println(s) fmt.Println(len(s)) fmt.Println(cap(s))
|
new 的详细用法
基本类型
p := new(int) *p = 42 fmt.Println(*p)
s := new(string) *s = "hello" fmt.Println(*s)
b := new(bool) fmt.Println(*b)
|
new(int) 返回 *int,指向一个值为 0 的 int。
结构体
type User struct { ID int Name string }
p := new(User) fmt.Printf("%+v\n", *p) p.ID = 1 p.Name = "Tom" fmt.Printf("%+v\n", *p)
|
new(User) 等价于 &User{},都返回指向零值结构体的指针:
p1 := new(User) p2 := &User{} fmt.Println(*p1 == *p2)
|
但 &User{Name: "Tom"} 可以在创建时初始化字段,new(User) 只能得到零值,所以实际开发中 &User{...} 更常用。
数组
p := new([3]int) fmt.Println(*p)
p[0] = 1 fmt.Println(*p)
|
new([3]int) 返回 *[3]int,指向一个全零数组。
自定义类型
type Celsius float64
p := new(Celsius) *p = 36.5 fmt.Println(*p)
|
new 对任何类型都适用,包括自定义类型、函数类型、接口类型等。
接口类型
var p = new(io.Reader) fmt.Println(p) fmt.Println(*p)
|
new(io.Reader) 返回 *io.Reader,指向一个 nil 接口值。这种用法很少见,但可以说明 new 适用于任意类型。
重点坑:new 用于 slice、map、chan
new 可以用于这三种类型,但返回的是指向 nil 值的指针,内部结构并没有初始化。
new([]int)
p := new([]int) fmt.Println(p) fmt.Println(*p) fmt.Println(*p == nil)
*p = append(*p, 1) fmt.Println(*p)
|
new([]int) 得到 *[]int,*p 是 nil slice。nil slice 可以 append,但不能直接索引。
new(map[string]int)
p := new(map[string]int) fmt.Println(p) fmt.Println(*p == nil)
*p = make(map[string]int) (*p)["a"] = 1 fmt.Println(*p)
|
new(map...) 得到 *map,*p 是 nil map。nil map 可以读,但写入会 panic,必须先 make 再赋值。
new(chan int)
p := new(chan int) fmt.Println(*p == nil)
*p = make(chan int, 1) *p <- 1 fmt.Println(<-*p)
|
new(chan int) 得到 *chan int,*p 是 nil channel。nil channel 的发送和接收都会永久阻塞,close 会 panic。
new 小结
- 返回
*T;
- 只做零值分配,不做内部初始化;
- 对 slice/map/chan 使用
new 通常不是想要的结果;
- 结构体场景通常用
&T{...} 替代 new(T)。
make 的详细用法
make 只能用于三种类型:
make([]T, len, cap) make(map[K]V, hint) make(chan T, buffer)
|
make 创建 slice
s1 := make([]int, 3) fmt.Println(s1, len(s1), cap(s1)) s1[0] = 1 fmt.Println(s1)
s2 := make([]int, 0, 5) fmt.Println(s2, len(s2), cap(s2)) s2 = append(s2, 1, 2, 3) fmt.Println(s2, len(s2), cap(s2))
|
关键区别:
s1 := make([]int, 3) s2 := make([]int, 0, 3)
|
make([]int, 0, 3) 常用于预分配容量,避免 append 过程中多次扩容:
s := make([]int, 0, 1000) for i := 0; i < 1000; i++ { s = append(s, i) }
|
注意:make([]int, 5, 3) 是非法参数,len 不能大于 cap。常量情况下编译报错,变量情况下运行时 panic。
make 创建 map
m1 := make(map[string]int) m1["a"] = 1 fmt.Println(m1)
m2 := make(map[string]int, 100) for i := 0; i < 100; i++ { m2[fmt.Sprintf("key-%d", i)] = i }
|
make(map[K]V, hint) 的 hint 只是容量提示,不是硬性上限,实际容量由运行时决定。
对比 nil map:
var m1 map[string]int fmt.Println(m1["a"])
m2 := make(map[string]int) m2["a"] = 1
|
make 创建 channel
ch1 := make(chan int) go func() { ch1 <- 1 }() fmt.Println(<-ch1)
ch2 := make(chan int, 3) ch2 <- 1 ch2 <- 2 ch2 <- 3
fmt.Println(<-ch2)
|
对比 nil channel:
make 不能用于其他类型
make 的第一个参数必须是 slice、map 或 channel 类型。
new 与 make 对比
| 特性 |
new |
make |
| 适用类型 |
任意类型 |
仅 slice、map、chan |
| 返回值 |
*T |
T |
| 是否初始化内部结构 |
否,只分配零值 |
是 |
| 典型用途 |
获取零值指针 |
创建可用的引用类型 |
| 常用程度 |
较低 |
非常高 |
| 示例 |
p := new(int) |
s := make([]int, 0, 10) |
记忆口诀:
new 返回指针,只清零;make 返回本身,做初始化。
综合示例:正确初始化一个包含引用类型字段的结构体
type Bucket struct { Items []string Index map[string]int Ch chan string }
func NewBucket() *Bucket { return &Bucket{ Items: make([]string, 0, 10), Index: make(map[string]int), Ch: make(chan string, 1), } }
func main() { b := NewBucket() b.Items = append(b.Items, "a") b.Index["a"] = 0 b.Ch <- "a" fmt.Println(<-b.Ch) }
|
如果用 new 创建并忘记初始化:
b := new(Bucket)
b.Items = append(b.Items, "a")
b.Index = make(map[string]int) b.Index["a"] = 0
b.Ch = make(chan string, 1) b.Ch <- "a"
|
所以更推荐用构造函数 NewBucket() 集中初始化。
常见坑汇总
坑 1:new 创建 map 后直接写入
m := new(map[string]int) (*m)["a"] = 1
*m = make(map[string]int) (*m)["a"] = 1
|
坑 2:new 创建 slice 后直接索引
s := new([]int) (*s)[0] = 1
*s = make([]int, 1) (*s)[0] = 1
|
坑 3:new 创建 chan 后收发
ch := new(chan int) *ch <- 1
*ch = make(chan int, 1) *ch <- 1
|
坑 4:make 用于结构体
u := new(User)
u := &User{}
|
坑 5:make slice 的 len 和 cap 混淆
s := make([]int, 5)
s2 := make([]int, 0, 5)
|
坑 6:nil slice 和空 slice 的 JSON 差异
var s1 []int s2 := make([]int, 0)
b1, _ := json.Marshal(s1) b2, _ := json.Marshal(s2) fmt.Println(string(b1)) fmt.Println(string(b2))
|
需要输出 [] 而不是 null 时,用 make([]T, 0)。
选择建议
- 创建 slice、map、channel:一律用
make。
- 需要零值指针:可以用
new(T),但结构体通常写 &T{} 更直观。
- 需要带初始值的结构体指针:用
&T{Field: value},不要用 new 再逐个赋值。
- 不要用
new 创建 map/slice/chan 后忘记初始化,这是最常见的误用。
- 预分配容量:slice 用
make([]T, 0, n),map 用 make(map[K]V, hint),channel 用 make(chan T, buffer)。
一句话总结
new(T):分配零值,返回 *T,不初始化内部结构。
make(T, args...):初始化 slice/map/chan,返回 T 本身。
- 实际开发中:
make 用于引用类型,&T{...} 用于结构体,new 用得较少。