概述:
在 Go 语言中,make 和 new 是两个用于内存分配的内置函数,但它们的使用场景和返回值有所不同,下面我们从基本概念、使用场景、代码示例和源码层面分别介绍他们的区别。
基本概念:
new 函数
- 作用:new 是一个分配内存的内置函数,它为类型 T 分配零值内存,并返回指向该内存的指针
- 返回值:
new(T)返回的是*T,即指向类型 T 的指针。 - 适用类型:
new适用于所有类型,包括基本类型、结构体、数组等。 - 示例:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 使用 new 分配一个 Person 结构体,并返回指针
p := new(Person)
fmt.Println(p) // 输出: &{ 0}
fmt.Println(*p) // 输出: { 0}
fmt.Println(p.Name) // 输出: ""
fmt.Println(p.Age) // 输出: 0
// 修改结构体的值
p.Name = "Alice"
p.Age = 30
fmt.Println(*p) // 输出: {Alice 30}
// 使用 new 分配一个 int 类型,并返回指针
i := new(int)
fmt.Println(i) // 输出: 0x... (指针地址)
fmt.Println(*i) // 输出: 0
*i = 10
fmt.Println(*i) // 输出: 10
}
make 函数
- 作用:make 也是一个分配内存的内置函数,但是它仅仅用于创建特定类型的对象,如 slice、map、channel
- 返回值:
make返回的是一个特定类型的初始化后的值,是经过初始化之后的特定类型的引用。 - 适用类型:
make仅适用于slice、map和channel类型。 - 示例:
package main
import "fmt"
func main() {
// 创建一个长度为 5 的 int 切片
s := make([]int, 5)
fmt.Println(s) // 输出: [0 0 0 0 0]
s[0] = 10
fmt.Println(s) // 输出: [10 0 0 0 0]
// 创建一个容量为 10 的切片
s2 := make([]int, 5, 10)
fmt.Println(s2) // 输出: [0 0 0 0 0]
fmt.Println(len(s2)) // 输出: 5
fmt.Println(cap(s2)) // 输出: 10
// 创建一个 map
m := make(map[string]int)
fmt.Println(m) // 输出: map[]
m["a"] = 1
m["b"] = 2
fmt.Println(m) // 输出: map[a:1 b:2]
// 创建一个无缓冲的 channel
ch := make(chan int)
go func() {
ch <- 1
}()
fmt.Println(<-ch) // 输出: 1
// 创建一个带缓冲的 channel
ch2 := make(chan int, 2)
ch2 <- 10
ch2 <- 20
fmt.Println(<-ch2) // 输出: 10
fmt.Println(<-ch2) // 输出: 20
}
使用场景:
new 的使用场景
- 分配结构体:当你需要一个指向结构体的指针时,可以使用 new。
type Person struct {
Name string
Age int
}
p := new(Person)
- 分配基本类型:当你需要一个指向基本类型的指针时,可以使用 new。
i := new(int)
make 的使用场景:
- 创建切片:当你需要一个切片时,可以使用 make。
s := make([]int, 5) // 创建一个长度为 5 的 int 切片
- 创建映射:当你需要一个映射时,可以使用 make。
m := make(map[string]int) // 创建一个 string 到 int 的映射
- 创建通道:当你需要一个通道时,可以使用 make。
ch := make(chan int) // 创建一个 int 类型的通道
源码层面:
new 的源码实现
new 的实现非常简单,它直接调用了 runtime.newobject 函数。
// src/runtime/malloc.go
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
- newobject 函数会根据类型的大小 typ.size 分配内存,并返回一个指向该内存的指针。
- 分配的内存会被初始化为该类型的零值。
make 的源码实现
make 的实现稍微复杂一些,因为它需要处理不同类型的初始化。
- 切片:make([]T, len, cap) 会调用 runtime.makeslice 函数。
// src/runtime/slice.go
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 计算需要的内存大小并分配内存
// 初始化切片结构体
}
- 映射:make(map[K]V, hint) 会调用 runtime.makemap 函数。
// src/runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {
// 初始化哈希表结构
}
- 通道:make(chan T, size) 会调用 runtime.makechan 函数。
// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
// 初始化通道结构
}
总结
- new:用于分配内存并返回指向该内存的指针,适用于任何类型,但不会进行额外的初始化。
- make:用于创建 slice、map 和 channel,不仅分配内存,还会进行初始化,返回的是值而不是指针。