Go 语言中的 make 与 new 使用区别与技巧
Go 语言的内存分配有两种常用方式:make 和 new。虽然它们都用于分配内存,但它们的作用和使用场景有所不同。理解这两者的区别对于编写高效和可维护的 Go 代码至关重要。本文将详细解析 make 和 new 的区别、适用场景,并提供一些使用技巧。
1. make 和 new 的基本区别
1.1 new:创建指针类型的零值
new 是 Go 中用于分配内存的关键字,它的作用是为一个类型分配一块内存,并返回一个指向这块内存的指针。使用 new 分配的内存被初始化为类型的零值。
示例:使用 new
package main
import "fmt"
func main() {
var p *int = new(int)
fmt.Println(*p) // 输出: 0,`new` 为 int 分配的内存被初始化为零值
}
- 返回类型:
new返回的是指向类型的指针。 - 零值初始化:
new会将分配的内存初始化为类型的零值。例如,对于int类型,零值是0;对于string类型,零值是空字符串""。
1.2 make:初始化切片、映射和通道
make 是 Go 特有的一个内建函数,专门用于初始化三种内建数据类型:切片(slice)、映射(map)和通道(channel)。与 new 不同,make 不返回一个指向内存的指针,而是返回初始化后的对象本身。
示例:使用 make
package main
import "fmt"
func main() {
// 初始化切片
s := make([]int, 5)
fmt.Println(s) // 输出: [0 0 0 0 0]
// 初始化映射
m := make(map[string]int)
m["age"] = 30
fmt.Println(m) // 输出: map[age:30]
// 初始化通道
ch := make(chan int, 2)
ch <- 1
fmt.Println(<-ch) // 输出: 1
}
- 返回类型:
make返回的是切片、映射或通道类型的对象,而不是指针。 - 内存分配与初始化:
make不仅分配内存,还会初始化数据结构本身。例如,在初始化切片时,make会为切片分配底层数组并设置其长度和容量。
1.3 主要区别总结
| 特性 | new | make |
|---|---|---|
| 用途 | 分配内存并返回指向类型的指针 | 初始化并返回切片、映射、通道对象 |
| 返回值 | 返回类型的指针 | 返回初始化后的对象本身 |
| 适用类型 | 所有类型 | 切片、映射、通道 |
| 初始化 | 返回零值 | 根据数据结构的类型进行初始化 |
2. make 和 new 的使用技巧
2.1 new 的使用技巧
- 适用于结构体类型:
new常用于为结构体分配内存并返回指针。需要注意的是,结构体指针的初始化值是结构体的零值。
示例:使用 new 为结构体分配内存
type Person struct {
Name string
Age int
}
func main() {
p := new(Person)
fmt.Println(p) // 输出: &{ 0}
fmt.Println(p.Name) // 输出: 空字符串
fmt.Println(p.Age) // 输出: 0
}
- 通过
new创建的指针:由于new返回的是指向结构体的指针,可以直接使用p.Name或p.Age修改其字段值。
2.2 make 的使用技巧
- 初始化带容量的切片:
make可用于初始化一个具有指定长度和容量的切片。通过make,可以高效地分配底层数组并初始化切片。
示例:使用 make 初始化带容量的切片
// 初始化一个长度为 5,容量为 10 的切片
s := make([]int, 5, 10)
fmt.Println(len(s), cap(s)) // 输出: 5 10
- 初始化映射时指定容量:在使用
make创建映射时,可以指定初始的容量,这有助于优化性能,避免在插入元素时进行多次内存扩展。
示例:使用 make 初始化映射
m := make(map[string]int, 10) // 指定容量为 10
m["age"] = 30
m["height"] = 175
fmt.Println(m) // 输出: map[age:30 height:175]
- 初始化带缓冲区的通道:使用
make创建带缓冲区的通道,指定通道的缓冲区大小,这在并发程序中非常有用。
示例:使用 make 创建带缓冲区的通道
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) // 输出: 1
2.3 选择合适的内存分配方式
- 结构体的使用场景:如果你只是需要一个结构体的指针,并且初始化时没有特别的需求,使用
new是一种简单且常见的方式。 - 切片、映射和通道的使用场景:如果你需要初始化一个切片、映射或通道并且可能会修改它们的内容,
make是更合适的选择,特别是在你需要提前指定容量时。
3. make 和 new 的性能考虑
- 内存分配开销:
make在初始化切片、映射和通道时不仅分配内存,还执行类型初始化,这可能会带来额外的开销。而new仅分配内存并初始化为零值,因此其开销较小。 - 避免不必要的内存分配:对于切片、映射或通道等类型,尽量使用
make时指定合适的容量,以减少内存重分配的次数。
4. 常见的错误用法
- 误用
new创建切片或映射:new用于切片、映射和通道时,只会返回其类型的零值,而不进行初始化,因此如果使用new创建切片、映射或通道并尝试直接访问其内容,会导致运行时错误。
错误示例:误用 new 创建映射
m := new(map[string]int) // 错误:返回的是一个指针而不是已初始化的映射
m["age"] = 30 // 运行时错误:m 是 nil
- 正确示例:应该使用
make来初始化映射。
m := make(map[string]int)
m["age"] = 30
5. 总结
Go 语言中的 make 和 new 作为内存分配的关键字,虽然功能相似,但有着明确的区别。new 用于为类型分配内存并返回指针,适用于大多数类型;而 make 主要用于初始化切片、映射和通道,提供了更强的初始化功能。
new:适用于创建结构体类型和其他基础类型的指针,并将内存初始化为零值。make:用于初始化切片、映射和通道,支持指定容量并完成内部初始化。
理解这两者的不同使用场景和性能影响,有助于编写更高效和可维护的 Go 代码。