make 和 new 的区别是什么?
在 Go 语言中,make 和 new 都是用于内存分配的内建函数,但它们的作用对象、返回类型以及底层行为有非常明显的区别。
简单总结:
new:只分配内存,不初始化内存(只置为零值),返回指针。make:分配内存并初始化内部数据结构,返回值(仅用于 slice, map, channel)。
详细对比
1. new 函数
- 语法:
func new(Type) *Type - 作用:
- 为类型
Type分配一块内存。 - 将这块内存的内容清零(即设置为该类型的零值,如 int 为 0,bool 为 false)。
- 返回指向这块内存的指针(
*Type)。
- 为类型
- 适用类型:任意类型(包括基本类型、结构体等)。
- 类比:就像给你一个空盒子,里面什么都没有(或者说是默认的填充物),但把盒子的地址给了你。
示例:
go
p := new(int) // p 的类型是 *int
fmt.Println(*p) // 输出 0 (int 的零值)
type Student struct {
Name string
Age int
}
s := new(Student) // s 的类型是 *Student
fmt.Println(s.Name) // 输出 "" (string 的零值)
2. make 函数
- 语法:
func make(t Type, size ...IntegerType) Type - 作用:
- 分配内存。
- 初始化底层的数据结构(不仅仅是清零,还包括设置长度、容量、哈希桶、等待队列等)。
- 返回该类型的值(
Type),而不是指针。
- 适用类型:仅限 slice(切片)、map(字典) 和 channel(通道)。
- 原因:这三种类型在 Go 内部是复杂的结构,必须经过初始化才能使用。例如,slice 内部包含指向底层数组的指针、长度和容量;如果只用
new分配内存而不初始化这些字段,slice 就无法正常工作。 - 类比:就像给你一台手机,不仅硬件(内存)准备好了,操作系统也启动了(初始化),你可以直接拿来用。
示例:
go
// 创建一个包含 10 个整数的切片
s := make([]int, 10) // s 的类型是 []int
s[0] = 100 // 可以直接使用
// 创建一个 map
m := make(map[string]int) // m 的类型是 map[string]int
m["key"] = 1 // 可以直接赋值
// 创建一个 channel
c := make(chan int) // c 的类型是 chan int
核心区别总结表
| 特性 | new(T) |
make(T, args) |
|---|---|---|
| 作用对象 | 任意类型 (int, struct, array 等) | 仅限 slice, map, channel |
| 返回类型 | 指针 *T |
值 T (这三种类型本身就是引用语义) |
| 内存状态 | 分配内存并置为零值 | 分配内存并初始化内部结构 |
| 主要用途 | 获取指向零值的指针 | 创建可用的引用类型对象 |
为什么 make 不返回指针?
这是一个常见的问题。因为 slice、map 和 channel 在 Go 中属于引用类型。
- Slice:本质上是一个结构体,包含
{ptr, len, cap}。 - Map 和 Channel:本质上是指向底层复杂结构的指针。
当你使用 make 时,你想要的是那个可以直接操作的“句柄”(handle)。如果 make 返回指针,你得到的就是“指向 slice 结构体的指针”,在使用时会非常麻烦(需要解引用)。
错误用法示例
如果你尝试用 new 来创建 map,会发生 panic,因为 map 没有被初始化:
go
// 错误示范
var m *map[string]int
m = new(map[string]int) // 分配了 map 的指针,但 map 内部结构未初始化
// (*m)["key"] = 1 // 运行时错误:panic: assignment to entry in nil map
// 正确示范
m2 := make(map[string]int)
m2["key"] = 1 // 正常工作
总结
- 如果你需要一个 slice、map 或 channel,请使用
make。 - 如果你需要结构体或基本类型的指针,请使用
new(或者更常用的字面量语法&Type{})。