Golang new 和 make 的详解
在 Go 语言中,
new和make是两个用于分配内存的内建函数,但它们的应用场景和行为有显著区别。理解这两者的不同是 Go 语言初学者常常遇到的挑战之一,也是掌握 Go 内存管理和数据结构使用方式的关键。简而言之,new主要用于分配零值内存并返回指向该内存的指针,而make主要用于初始化切片 (slice)、映射 (map) 和通道 (channel) 这三种引用类型,并返回已初始化的类型本身(而非指针)。
核心思想:
new:- 分配内存:为任何类型分配内存。
- 返回指针:返回一个指向新分配内存的指针。
- 零值初始化:将分配的内存初始化为该类型的零值。
- 适用类型:值类型 (struct, array, int, bool等) 和引用类型 (slice, map, channel) 的指针。
make:- 初始化引用类型:仅用于切片 (slice)、映射 (map) 和通道 (channel) 这三种引用类型。
- 返回类型本身:返回一个已初始化的引用类型实例(而非指针)。
- 非零值初始化:为这三种类型分配并初始化底层数据结构,使其处于可用状态。
- 适用类型:slice, map, channel。
一、new 函数详解
new 是一个内建函数,其签名大致为 func new(Type) *Type。
1.1 new 的行为
- 分配内存:
new(Type)为Type类型的值分配一片内存空间。 - 零值初始化:分配的内存会被初始化为
Type类型的零值。- 对于数值类型(
int,float等),零值为0。 - 对于布尔类型(
bool),零值为false。 - 对于字符串类型(
string),零值为""(空字符串)。 - 对于指针类型,零值为
nil。 - 对于结构体(
struct),所有字段都会被初始化为其各自的零值。 - 对于数组(
array),所有元素都会被初始化为其各自的零值。
- 对于数值类型(
- 返回指针:
new函数返回一个指向新分配且已初始化为零值的内存空间的指针(类型为*Type)。
1.2 new 的使用场景
new 可以用于任何类型,包括值类型和引用类型,但它最常见的用途是为结构体或基本类型分配内存并获取其指针。
1.2.1 为基本类型分配内存
1 | package main |
1.2.2 为结构体 struct 分配内存
1 | package main |
注意:对于结构体,更常见的做法是使用结构体字面量 &Person{} 来创建并获取指针,因为它允许在创建时同时初始化字段,比 new 更灵活。
1 | p1 := new(Person) // {Name:"" Age:0} |
1.2.3 为引用类型分配内存(但不推荐直接使用 new)
虽然 new 可以为 slice、map、channel 类型分配内存,但分配后它们的值是 nil。nil 的 slice、map、channel 是无法直接使用的。
1 | package main |
可以看到,直接用 new 分配 slice 和 map 后,它们的值仍然是 nil(或者说 *[]int 解引用后是一个空切片,但底层数组未分配),不能进行实际的元素操作。因此,对于这三种引用类型,我们强烈建议使用 make 来初始化它们。
二、make 函数详解
make 也是一个内建函数,但它仅用于创建并初始化 slice、map 和 channel 这三种引用类型。它返回的是已初始化的类型本身(而非指针)。
2.1 make 的签名和行为
make 的签名因类型而异,无法统一写出,但其核心行为是:
- 分配内存并初始化底层数据结构:
make不仅分配类型本身所需的内存,还会为这三种引用类型分配并初始化其底层数据结构(例如,slice的底层数组,map的哈希表结构,channel的缓冲区)。 - 返回类型本身:
make返回一个已准备好使用的slice、map或channel实例,而不是指向它们的指针。
2.2 make 的使用场景和参数
2.2.1 make 用于 slice
make([]Type, len, cap),其中:
Type:切片元素的类型。len:切片的初始长度。cap(可选):切片的容量。如果省略,容量等于长度。
1 | package main |
总结 slice 的初始化:
var s []int:创建一个nil切片,长度和容量都为 0。可以安全地使用append。s := []int{}:创建一个空切片,但不是nil,其底层数组已存在(虽然大小为 0)。也可以安全地使用append。s := make([]int, length, capacity):创建指定长度和容量的切片。适用于预知大小或需要预分配内存的场景。
2.2.2 make 用于 map
make(map[KeyType]ValueType, capacity),其中:
KeyType:映射键的类型。ValueType:映射值的类型。capacity(可选):映射的初始容量,Go 会预先分配足够的空间,以减少后续扩容的开销。
1 | package main |
总结 map 的初始化:
var m map[KeyType]ValueType:创建一个nilmap。不能直接添加元素。m := map[KeyType]ValueType{}:创建一个空的 map,底层哈希表已初始化,可以立即添加元素。m := make(map[KeyType]ValueType, capacity):创建指定初始容量的 map。推荐用于预知元素数量的场景,以优化性能。
2.2.3 make 用于 channel
make(chan Type, buffer_capacity),其中:
Type:通道中传输的数据类型。buffer_capacity(可选):通道的容量。0或省略:创建一个无缓冲通道(unbuffered channel)。发送和接收操作会立即阻塞,直到另一端就绪。> 0:创建一个有缓冲通道(buffered channel)。发送操作在缓冲区满时阻塞,接收操作在缓冲区空时阻塞。
1 | package main |
总结 channel 的初始化:
var c chan Type:创建一个nilchannel。对nilchannel 的发送和接收操作都会永久阻塞。c := make(chan Type):创建一个无缓冲通道。c := make(chan Type, capacity):创建一个有缓冲通道。
三、new 与 make 的本质区别总结
| 特性 | new(Type) |
make(Type, args) |
|---|---|---|
| 用途 | 分配内存并初始化为零值 | 初始化 slice、map 或 channel 的底层数据结构 |
| 返回类型 | *Type (指向 Type 的指针) |
Type 本身 (已初始化的 slice、map 或 channel 实例) |
| 适用类型 | 任何类型 (值类型、引用类型) | 仅限于 slice、map、channel |
| 初始化内容 | 分配的内存初始化为零值 | 为引用类型分配并初始化底层数据结构,使其可用 |
| 是否可用 | 返回的指针指向零值,可以直接通过 *p 访问和赋值,但对于 slice/map 等,解引用后仍是 nil 或空但未分配底层空间,不能直接使用其元素操作。 |
返回的类型实例已准备就绪,可立即进行添加元素、发送/接收等操作。 |
图示总结
graph TD
%% 黑暗模式主题配置
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#2d333b', 'edgeColor': '#adbac7'}}}%%
subgraph GoMemoryManagement [Go 内存管理]
direction TB
%% new 流程
A["new(T)"] --> B{"分配内存"}
B --> C["初始化为零值"]
C --> D["返回 *T 指针"]
%% make 流程
E["make(T, ...)"] --> F{"初始化内部结构"}
F --> G1["Slice: 创建底层数组"]
F --> G2["Map: 创建哈希表"]
F --> G3["Chan: 创建缓冲区"]
G1 --> H["返回 []T"]
G2 --> I["返回 map[K]V"]
G3 --> J["返回 chan T"]
end
%% 样式美化
classDef newBox fill:#1f2937,stroke:#3b82f6,stroke-width:2px,color:#f3f4f6;
classDef makeBox fill:#1f2937,stroke:#10b981,stroke-width:2px,color:#f3f4f6;
classDef decision fill:#374151,stroke:#f59e0b,stroke-width:2px,color:#fbbf24;
class A,B,C,D newBox;
class E,F,G1,G2,G3,H,I,J makeBox;
class B,F decision;
四、何时使用 new,何时使用 make?
使用
new的场景:- 你需要为一个值类型(如
struct,[N]Type,int等)或想要获取指针。 - 你需要为引用类型(如
slice,map,channel)获取指针,但请注意,此时通常还需要make来初始化这个引用类型的值。例如myMapPtr := new(map[string]int); *myMapPtr = make(map[string]int),但实际中这种用法非常少见,直接myMap := make(map[string]int)更加简洁直接。
- 你需要为一个值类型(如
使用
make的场景:- 当你需要创建
slice、map或channel时。这是唯一正确的初始化这些引用类型的方式。 - 你需要预先分配这些数据结构的容量,以优化性能。
- 当你需要创建
示例代码对比
1 | package main |
五、深入理解 Go 语言中的零值
零值 (zero value) 是 Go 语言的一个重要概念。当一个变量被声明但未显式初始化时,它会自动获得其类型的零值。
- 值类型的零值是其内存位的全零表示。
int,float,byte,rune等:0bool:falsestring:""(空字符串)array: 所有元素都是其各自类型的零值struct: 所有字段都是其各自类型的零值
- 引用类型的零值是
nil。*Type(指针):nil[]Type(切片):nilmap[Key]Value(映射):nilchan Type(通道):nilfunc(...)(函数):nilinterface{}(接口):nil
new 函数的工作就是创建一个指向零值的指针。例如,new(Config) 实际上创建了一个 Config 类型的零值并返回其指针。由于 Config 结构体中的 Servers 是 []string 类型(切片),它的零值是 nil;Options 是 map[string]string 类型,它的零值也是 nil;LogChan 是 chan string 类型,其零值也是 nil。这就是为什么 new 创建的 Config 结构体中的引用类型字段需要额外 make 来初始化才能使用的原因。
理解 Go 的零值概念,有助于更深入地掌握 new 和 make 的行为以及如何正确地初始化各种数据结构。
