
go语言提供了多种方式来分配内存和初始化值,包括:
理解这些方式的细微差别,尤其是new和make,是掌握Go语言内存管理的基石。
new是一个内建函数,其主要作用是为指定类型分配内存,并将这块内存清零(即初始化为该类型的零值),然后返回一个指向这块内存的指针。new函数可以应用于Go语言中的任何类型。
语法: new(Type)返回值: *Type (指向新分配的零值内存的指针)
示例:
立即学习“go语言免费学习笔记(深入)”;
package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
// 1. 分配一个整型并获取其指针
ptrInt := new(int)
fmt.Printf("new(int) -> 类型: %T, 值: %v, 指向的值: %v\n", ptrInt, ptrInt, *ptrInt) // 类型: *int, 值: 0xc000018080, 指向的值: 0
// 2. 分配一个Point结构体并获取其指针
ptrPoint := new(Point)
fmt.Printf("new(Point) -> 类型: %T, 值: %v, 指向的值: %v\n", ptrPoint, ptrPoint, *ptrPoint) // 类型: *main.Point, 值: 0xc000018088, 指向的值: {0 0}
// 3. 对比使用复合字面量获取指针
// &Point{} 同样分配并返回一个指向Point零值的指针
// &Point{2, 3} 分配并初始化,然后返回指针
initializedPointPtr := &Point{2, 3}
fmt.Printf("&Point{2, 3} -> 类型: %T, 值: %v, 指向的值: %v\n", initializedPointPtr, initializedPointPtr, *initializedPointPtr) // 类型: *main.Point, 值: 0xc000018090, 指向的值: {2 3}
// 注意:&int 是非法的,不能直接获取类型字面量的地址
// var i int; &i 是合法的,但不如 new(int) 简洁
var i int
ptrI := &i
fmt.Printf("var i int; &i -> 类型: %T, 值: %v, 指向的值: %v\n", ptrI, ptrI, *ptrI) // 类型: *int, 值: 0xc000018098, 指向的值: 0
}从示例中可以看出,new为所有类型分配内存并将其初始化为零值,然后返回一个指向该零值的指针。
make也是一个内建函数,但它与new有着本质的区别。make仅用于创建并初始化切片(slice)、映射(map)和通道(channel)这三种引用类型。它不仅分配内存,还会初始化这些类型内部的数据结构,使其可以立即使用。make返回的是这些类型的值,而不是指针。
语法:
示例:
立即学习“go语言免费学习笔记(深入)”;
package main
import "fmt"
func main() {
// 1. 使用 make 创建一个切片
// make([]int, 5) 创建一个长度为5,容量为5的int切片,元素初始化为零值
slice := make([]int, 5)
fmt.Printf("make([]int, 5) -> 类型: %T, 值: %v, 长度: %d, 容量: %d\n", slice, slice, len(slice), cap(slice)) // 类型: []int, 值: [0 0 0 0 0], 长度: 5, 容量: 5
// 2. 使用 make 创建一个映射
// make(map[string]int) 创建一个空的map
myMap := make(map[string]int)
myMap["apple"] = 1
fmt.Printf("make(map[string]int) -> 类型: %T, 值: %v, 长度: %d\n", myMap, myMap, len(myMap)) // 类型: map[string]int, 值: map[apple:1], 长度: 1
// 3. 使用 make 创建一个通道
// make(chan int) 创建一个无缓冲的int通道
// make(chan int, 3) 创建一个带3个缓冲的int通道
ch := make(chan int, 2)
fmt.Printf("make(chan int, 2) -> 类型: %T, 值: %v\n", ch, ch) // 类型: chan int, 值: 0xc0000180c0
// 错误示例:make 不能用于非切片、映射、通道类型
// make(Point) // 编译错误: cannot make type Point
// make(int) // 编译错误: cannot make type int
}make函数返回的是一个已经初始化好的值,可以直接使用。它为这些引用类型分配了底层的存储空间,并设置了必要的内部结构(如切片的长度、容量,映射的哈希表,通道的缓冲区等)。
通过上述分析,我们可以总结出new和make的关键差异:
| 特性 | new(Type) | make(Type, ...) |
|---|---|---|
| 用途 | 为任意类型分配零值内存 | 仅用于切片、映射、通道的分配与初始化 |
| 返回值 | 返回指向零值内存的指针 (*Type) | 返回已初始化的值 (Type) |
| 功能 | 仅分配内存并清零 | 分配内存并初始化内部数据结构,使其可立即使用 |
| 适用类型 | 所有类型(基本类型、结构体、数组、指针等) | 仅限切片([]T)、映射(map[K]V)、通道(chan T) |
为什么Go语言要设计两个不同的函数?
最初,Go语言的开发者也考虑过将new和make合并成一个单一的内建函数。然而,最终决定保持它们的分离,主要出于以下考量:
// 假设只有一个ALLOC函数 p := ALLOC(*chan int) // 需要 * 才能得到 *chan int c := ALLOC(chan int) // 直接得到 chan int s := ALLOC([]int, 10) // 直接得到 []int p_int := ALLOC(*int) // 需要 * 才能得到 *int
这种方式容易让新手混淆,何时需要*,何时不需要。
理解new和make的职责分离是Go语言设计哲学的一部分,它强调了明确性和简洁性,帮助开发者更好地管理内存和编写清晰的代码。
以上就是Go语言内存分配:深入解析new与make的异同与应用场景的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号