文章目录
-
- 一、基本概念对比
-
- [1.1 `new` 函数](#1.1
new
函数) - [1.2 `make` 函数](#1.2
make
函数) - [1.3 详细区别对比](#1.3 详细区别对比)
- [1.4 核心要点](#1.4 核心要点)
- [1.5 使用场景](#1.5 使用场景)
- [1.1 `new` 函数](#1.1
- 二、使用场景详解
-
- [2.1 什么时候使用 `new`](#2.1 什么时候使用
new
) - [2.2 什么时候使用 `make`](#2.2 什么时候使用
make
)
- [2.1 什么时候使用 `new`](#2.1 什么时候使用
- 三、常见错误
-
- [3.1 错误1:对slice/map/channel使用new](#3.1 错误1:对slice/map/channel使用new)
- [3.2 错误2:对非引用类型使用make](#3.2 错误2:对非引用类型使用make)
- [3.3 错误3:混淆返回类型](#3.3 错误3:混淆返回类型)
一、基本概念对比
程序的运行都需要内存,比如像变量的创建、函数的调用、数据的计算等。所以在需要内存的时候就要申请内存,进行内存分配。在 C/C++ 这类语言中,内存是由开发者自己管理的,需要主动申请和释放,而在 Go 语言中则是由该语言自己管理的,开发者不用做太多干涉,只需要声明变量,Go 语言就会根据变量的类型自动分配相应的内存。
Go 语言程序所管理的虚拟内存空间会被分为两部分:堆内存和栈内存。栈内存主要由 Go 语言来管理,开发者无法干涉太多,堆内存才是我们开发者发挥能力的舞台,因为程序的数据大部分分配在堆内存上,一个程序的大部分内存占用也是在堆内存上。
小提示:我们常说的 Go 语言的内存垃圾回收是针对堆内存的垃圾回收。
在Go语言中,new
和 make
是两个用于内存分配的内建函数,但它们有着完全不同的用途和行为。理解它们的区别对于编写高效、正确的Go代码至关重要。
1.1 new
函数
new
是一个用于分配内存的内建函数,它的主要功能是初始化一个类型的零值并返回其指针。
语法:func new(Type) *Type
特点
- 返回指向类型的指针(
*Type
) - 将内存初始化为该类型的零值
- 适用于所有类型
- 不初始化类型内部的数据结构(如slice的长度和容量)
示例
go
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 使用new分配结构体
p := new(Person)
fmt.Printf("Type: %T\n", p) // Type: *main.Person
fmt.Printf("Value: %+v\n", p) // Value: &{Name: Age:0}
fmt.Printf("Name: %s\n", p.Name) // Name: (零值)
fmt.Printf("Age: %d\n", p.Age) // Age: 0 (零值)
}
1.2 make
函数
make
是一个用于创建和初始化Go内置的引用类型的内建函数,如slice、map和channel。
语法:func make(t Type, size ...IntegerType) Type
特点
- 只能用于创建slice、map和channel
- 返回类型本身(不是指针)
- 会初始化类型的内部数据结构
- 必须指定类型相关的参数
示例
go
package main
import "fmt"
func main() {
// 创建slice
s := make([]int, 5, 10)
fmt.Printf("Type: %T\n", s) // Type: []int
fmt.Printf("Value: %v\n", s) // Value: [0 0 0 0 0]
fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s)) // Len: 5, Cap: 10
// 创建map
m := make(map[string]int)
fmt.Printf("Type: %T\n", m) // Type: map[string]int
fmt.Printf("Value: %v\n", m) // Value: map[]
// 创建channel
c := make(chan int, 1)
fmt.Printf("Type: %T\n", c) // Type: chan int
}
1.3 详细区别对比
特性 | new | make |
---|---|---|
返回类型 | 指针(*Type) | 类型本身(Type) |
适用类型 | 所有类型 | 仅slice、map、channel |
初始化内容 | 零值 | 完整初始化(包括内部结构) |
参数要求 | 只需类型 | 需要类型和大小参数 |
常见用途 | 创建结构体指针 | 创建可用的slice/map/channel |
分析 :new
通常比 make
更快,因为它只需要分配内存并设置为零值,而 make
还需要初始化内部数据结构。
1.4 核心要点
new
用于分配内存并返回指针,适用于所有类型make
用于创建和初始化slice、map、channel,返回类型本身- 对slice、map、channel必须使用make,不能使用new
- 对于结构体,优先使用字面量语法
&Type{}
- 理解零值初始化的概念,避免对未初始化的引用类型操作
1.5 使用场景
场景 | 推荐方式 | 替代方式 | 备注 |
---|---|---|---|
创建结构体指针 | &Type{} 或 new(Type) |
- | 字面量更清晰 |
创建基本类型指针 | new(Type) |
&Type{} |
基本类型没有字面量 |
创建slice | make([]T, len, cap) |
- | 唯一方式 |
创建map | make(map[K]V) |
- | 唯一方式 |
创建channel | make(chan T, size) |
- | 唯一方式 |
二、使用场景详解
2.1 什么时候使用 new
场景1:创建结构体指针
当你需要一个结构体的指针,并且希望它被初始化为零值时,使用 new
是最简洁的方式。
go
// 使用new
p := new(Person)
p.Name = "Alice"
p.Age = 30
// 等价于
p := &Person{}
p.Name = "Alice"
p.Age = 30
场景2:创建基本类型指针
go
// 创建int指针
ip := new(int)
*ip = 42
fmt.Println(*ip) // 42
// 创建string指针
sp := new(string)
*sp := "hello"
fmt.Println(*sp) // hello
场景3:创建数组指针
go
// 创建数组指针
ap := new([5]int)
(*ap)[0] = 100
fmt.Println(*ap) // [100 0 0 0 0]
2.2 什么时候使用 make
场景1:创建slice
make
是创建slice的标准方式,因为它可以同时指定长度和容量。
go
// 创建长度为5,容量为10的slice
s := make([]int, 5, 10)
// 等价于(更复杂的方式)
s := new([]int)
*s = make([]int, 5, 10)
场景2:创建map
make
是创建map的唯一方式,因为map需要初始化内部的数据结构。
go
// 正确方式
m := make(map[string]int)
m["key"] = 100
// 错误方式(会导致panic)
var m map[string]int
m["key"] = 100 // panic: assignment to entry in nil map
场景3:创建channel
make
是创建channel的唯一方式,因为channel需要初始化内部的数据结构。
go
// 创建带缓冲的channel
c := make(chan int, 10)
// 创建无缓冲的channel
c := make(chan int)
// 错误方式(会导致panic)
var c chan int
c <- 100 // panic: send on nil channel
三、常见错误
3.1 错误1:对slice/map/channel使用new
go
// 错误示例
s := new([]int)
(*s)[0] = 100 // panic: index out of range
// 正确方式
s := make([]int, 1)
s[0] = 100
分析 :new([]int)
返回的是一个指向nil slice的指针,这个slice的长度和容量都是0,所以不能直接赋值。
3.2 错误2:对非引用类型使用make
go
// 错误示例
p := make(Person) // 编译错误:cannot make type Person
// 正确方式
p := new(Person)
// 或
p := &Person{}
分析 :make
只能用于slice、map和channel,不能用于其他类型。
3.3 错误3:混淆返回类型
go
// 错误示例
var s *[]int
s = make([]int, 5) // 编译错误:cannot use make([]int, 5) (type []int) as type *[]int
// 正确方式
var s []int
s = make([]int, 5)
// 或
var s = new([]int)
*s = make([]int, 5)
分析 :make
返回的是类型本身,不是指针,不能直接赋值给指针变量。