Golang中make和new用法详解

慢慢就习惯了,情绪波动也没有那么大了,委屈了我就哭,我自己安慰自己

Posted by yishuifengxiao on 2026-10-28

一句话概括:

  • new(T):分配一块零值内存,返回 *T,适用于任意类型。
  • make(T, args...):初始化 slice、map、channel 的内部数据结构,返回 T 本身,只适用于这三种引用类型。

内建函数签名与语义

new(T)            // 返回 *T,指向 T 的零值
make(T, args...) // 返回 T,T 只能是 slice、map、chan

两者都是 Go 的内建函数,不需要 import,也不能被当作普通函数值使用。

new 的语义

new(T) 做两件事:

  1. 分配一块足够存放 T 的内存;
  2. 把这块内存清零,使其成为 T 的零值;
  3. 返回指向它的 *T。
p := new(int)
fmt.Println(*p) // 0
*p = 10
fmt.Println(*p) // 10

make 的语义

make(T, args...) 不是简单分配零值内存,而是初始化内部结构:

  • slice:分配底层数组,设置 len、cap,返回 slice header;
  • map:初始化哈希表,返回可读写的 map;
  • chan:初始化 channel 的缓冲区、锁、等待队列等。
s := make([]int, 3)
fmt.Println(s) // [0 0 0]
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 3

new 的详细用法

基本类型

p := new(int)
*p = 42
fmt.Println(*p) // 42

s := new(string)
*s = "hello"
fmt.Println(*s) // hello

b := new(bool)
fmt.Println(*b) // false

new(int) 返回 *int,指向一个值为 0 的 int。

结构体

type User struct {
ID int
Name string
}

p := new(User)
fmt.Printf("%+v\n", *p) // {ID:0 Name:}
p.ID = 1
p.Name = "Tom" // 指针自动解引用
fmt.Printf("%+v\n", *p) // {ID:1 Name:Tom}

new(User) 等价于 &User{},都返回指向零值结构体的指针:

p1 := new(User)
p2 := &User{}
fmt.Println(*p1 == *p2) // true

但 &User{Name: "Tom"} 可以在创建时初始化字段,new(User) 只能得到零值,所以实际开发中 &User{...} 更常用。

数组

p := new([3]int)
fmt.Println(*p) // [0 0 0]

p[0] = 1 // 数组指针支持索引简写,等价 (*p)[0] = 1
fmt.Println(*p) // [1 0 0]

new([3]int) 返回 *[3]int,指向一个全零数组。

自定义类型

type Celsius float64

p := new(Celsius)
*p = 36.5
fmt.Println(*p) // 36.5

new 对任何类型都适用,包括自定义类型、函数类型、接口类型等。

接口类型

var p = new(io.Reader)
fmt.Println(p) // <nil>,p 是 *io.Reader
fmt.Println(*p) // <nil>,*p 是 nil 接口

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) // true

// *p 是 nil slice
*p = append(*p, 1) // 可以,append 会创建新底层数组
fmt.Println(*p) // [1]

// (*p)[0] = 2 // panic: index out of range

new([]int) 得到 *[]int,*p 是 nil slice。nil slice 可以 append,但不能直接索引。

new(map[string]int)

p := new(map[string]int)
fmt.Println(p) // &map[]
fmt.Println(*p == nil) // true

// (*p)["a"] = 1 // panic: assignment to entry in nil map

*p = make(map[string]int)
(*p)["a"] = 1
fmt.Println(*p) // map[a:1]

new(map...) 得到 *map,*p 是 nil map。nil map 可以读,但写入会 panic,必须先 make 再赋值。

new(chan int)

p := new(chan int)
fmt.Println(*p == nil) // true

// *p <- 1 // 永久阻塞
// <-*p // 永久阻塞
// close(*p) // panic: close of nil channel

*p = make(chan int, 1)
*p <- 1
fmt.Println(<-*p) // 1

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)   // cap 可省略,默认等于 len
make(map[K]V, hint) // hint 可省略
make(chan T, buffer) // buffer 可省略,默认 0(无缓冲)

make 创建 slice

// len=3, cap=3
s1 := make([]int, 3)
fmt.Println(s1, len(s1), cap(s1)) // [0 0 0] 3 3
s1[0] = 1
fmt.Println(s1) // [1 0 0]

// len=0, cap=5
s2 := make([]int, 0, 5)
fmt.Println(s2, len(s2), cap(s2)) // [] 0 5
s2 = append(s2, 1, 2, 3)
fmt.Println(s2, len(s2), cap(s2)) // [1 2 3] 3 5

关键区别:

s1 := make([]int, 3)    // len=3,有三个零值元素,可索引 s1[0]
s2 := make([]int, 0, 3) // len=0,cap=3,不能索引 s2[0],但 append 不会立即扩容

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) // map[a:1]

// 带容量提示,预分配空间,减少扩容
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   // nil map
fmt.Println(m1["a"]) // 0,读安全
// m1["a"] = 1 // panic: assignment to entry in nil map

m2 := make(map[string]int)
m2["a"] = 1 // 正常

make 创建 channel

// 无缓冲 channel
ch1 := make(chan int)
go func() {
ch1 <- 1
}()
fmt.Println(<-ch1) // 1

// 有缓冲 channel,缓冲区大小为 3
ch2 := make(chan int, 3)
ch2 <- 1
ch2 <- 2
ch2 <- 3
// ch2 <- 4 // 阻塞,缓冲区已满
fmt.Println(<-ch2) // 1

对比 nil channel:

var ch chan int
// ch <- 1 // 永久阻塞
// <-ch // 永久阻塞
// close(ch) // panic: close of nil channel

make 不能用于其他类型

// make(int)          // 编译错误
// make(User) // 编译错误
// make([3]int) // 编译错误,数组不是 slice
// make(*int) // 编译错误

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) // a
}

如果用 new 创建并忘记初始化:

b := new(Bucket)
// b.Items 是 nil slice,append 可以但要注意
b.Items = append(b.Items, "a") // 可以

// b.Index["a"] = 0 // panic: assignment to entry in nil map
b.Index = make(map[string]int)
b.Index["a"] = 0 // 正确

// b.Ch <- "a" // 永久阻塞,nil channel
b.Ch = make(chan string, 1)
b.Ch <- "a" // 正确

所以更推荐用构造函数 NewBucket() 集中初始化。


常见坑汇总

坑 1:new 创建 map 后直接写入

m := new(map[string]int)
(*m)["a"] = 1 // panic: assignment to entry in nil map

// 正确
*m = make(map[string]int)
(*m)["a"] = 1

坑 2:new 创建 slice 后直接索引

s := new([]int)
(*s)[0] = 1 // panic: index out of range

// 正确
*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 := make(User) // 编译错误
u := new(User) // 正确
// 或
u := &User{} // 更常用

坑 5:make slice 的 len 和 cap 混淆

s := make([]int, 5)
// s[0] 到 s[4] 都存在,值为 0

s2 := make([]int, 0, 5)
// len=0,不能 s2[0],但 append 前 5 个元素不会扩容

坑 6:nil slice 和空 slice 的 JSON 差异

var s1 []int          // nil slice
s2 := make([]int, 0) // 非 nil 空 slice

b1, _ := json.Marshal(s1)
b2, _ := json.Marshal(s2)
fmt.Println(string(b1)) // null
fmt.Println(string(b2)) // []

需要输出 [] 而不是 null 时,用 make([]T, 0)。


选择建议

  1. 创建 slice、map、channel:一律用 make。
  2. 需要零值指针:可以用 new(T),但结构体通常写 &T{} 更直观。
  3. 需要带初始值的结构体指针:用 &T{Field: value},不要用 new 再逐个赋值。
  4. 不要用 new 创建 map/slice/chan 后忘记初始化,这是最常见的误用。
  5. 预分配容量: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 用得较少。