boxmoe_header_banner_img

Hello! 欢迎来到悠悠畅享网!

文章导读

Go语言内存分配:深入解析new与make的异同与应用场景


avatar
作者 2025年8月26日 10

Go语言内存分配:深入解析new与make的异同与应用场景

go语言中,new和make是两种核心的内存分配与初始化机制。new用于为任意类型分配零值内存并返回其指针,而make则专为切片、映射和通道这三种引用类型设计,用于分配并初始化其内部数据结构,返回的是已初始化的值而非指针。理解两者的差异及其适用场景,对于编写高效且符合Go惯例的代码至关重要。

Go语言中的内存分配与初始化方式

go语言提供了多种方式来分配内存和初始化值,包括:

  • 复合字面量(Composite Literals):如 Point{2, 3} 或 &Point{2, 3},后者结合了分配和初始化,并返回一个指向新分配结构的指针。
  • 局部变量地址:&someLocalVar,获取一个已存在局部变量的地址。
  • new 函数:分配零值内存并返回指针。
  • make 函数:为切片、映射、通道分配并初始化内存。

理解这些方式的细微差别,尤其是new和make,是掌握Go语言内存管理的基石。

new 函数:零值分配与指针返回

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, 指向的值: %vn", ptrInt, ptrInt, *ptrInt) // 类型: *int, 值: 0xc000018080, 指向的值: 0      // 2. 分配一个Point结构体并获取其指针     ptrPoint := new(Point)     fmt.Printf("new(Point) -> 类型: %T, 值: %v, 指向的值: %vn", 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, 指向的值: %vn", 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, 指向的值: %vn", ptrI, ptrI, *ptrI) // 类型: *int, 值: 0xc000018098, 指向的值: 0 }

从示例中可以看出,new为所有类型分配内存并将其初始化为零值,然后返回一个指向该零值的指针。

make 函数:特定类型初始化与值返回

make也是一个内建函数,但它与new有着本质的区别。make仅用于创建并初始化切片(slice)、映射(map)和通道(channel)这三种引用类型。它不仅分配内存,还会初始化这些类型内部的数据结构,使其可以立即使用。make返回的是这些类型的值,而不是指针。

语法:

  • make([]Type, Length, capacity) (切片)
  • make(map[KeyType]ValueType) (映射)
  • make(chan Type, capacity) (通道) 返回值: Type (已初始化的切片、映射或通道值)

示例:

立即学习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, 容量: %dn", 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, 长度: %dn", 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, 值: %vn", ch, ch) // 类型: chan int, 值: 0xc0000180c0      // 错误示例:make 不能用于非切片、映射、通道类型     // make(Point) // 编译错误: cannot make type Point     // make(int)   // 编译错误: cannot make type int }

make函数返回的是一个已经初始化好的值,可以直接使用。它为这些引用类型分配了底层的存储空间,并设置了必要的内部结构(如切片的长度、容量,映射的哈希表,通道的缓冲区等)。

new 与 make 的核心区别与设计哲学

通过上述分析,我们可以总结出new和make的关键差异:

特性 new(Type) make(Type, …)
用途 为任意类型分配零值内存 仅用于切片、映射、通道的分配与初始化
返回值 返回指向零值内存的指针 (*Type) 返回已初始化的值 (Type)
功能 仅分配内存并清零 分配内存并初始化内部数据结构,使其可立即使用
适用类型 所有类型(基本类型、结构体、数组、指针等) 仅限切片([]T)、映射(map[K]V)、通道(chan T)

为什么Go语言要设计两个不同的函数?

最初,Go语言的开发者也考虑过将new和make合并成一个单一的内建函数。然而,最终决定保持它们的分离,主要出于以下考量:

  1. 语义清晰性
    • new的语义是“分配一块内存并清零,然后给我它的地址”。这对于所有类型都是一致的。
    • make的语义是“创建一个可用的切片/映射/通道,并返回这个值”。这包含了更复杂的初始化逻辑,不仅仅是简单的内存分配。
  2. 操作的本质不同
    • new(T)返回的是*T,它是一个指针。
    • make(T, args)返回的是T,它是一个值。例如,make([]int, 10)返回的是一个切片值,而不是指向切片的指针。
    • 如果只有一个函数,例如ALLOC(Type, …),那么用户需要记住何时需要加*来获取指针,何时直接返回类型值。
      // 假设只有一个ALLOC函数 p := ALLOC(*chan int)   // 需要 * 才能得到 *chan int c := ALLOC(chan int)    // 直接得到 chan int s := ALLOC([]int, 10)   // 直接得到 []int p_int := ALLOC(*int)    // 需要 * 才能得到 *int

      这种方式容易让新手混淆,何时需要*,何时不需要。

  3. 避免混淆:两个函数的设计,使得开发者能够根据所需创建的类型和期望的返回结果(指针或值)直观地选择正确的函数,从而降低了学习曲线和出错的可能性。new用于分配“零值”的内存,而make用于“构建”一个可用的引用类型实例。

总结与实践建议

  • 当你需要为任何类型分配内存,并希望获得一个指向该类型零值的指针时,请使用 new。 例如,new(int)、new(MyStruct)。
  • 当你需要创建并初始化切片、映射或通道这三种引用类型时,请使用 make。 make会为你设置好这些类型所需的内部结构,使其立即可用。例如,make([]int, 10)、make(map[string]string)、make(chan bool)。
  • 对于结构体,如果想同时分配内存并初始化字段,通常使用复合字面量 &MyStruct{Field: value} 更为简洁和常见。 它既分配了内存,又进行了初始化,并返回一个指针。

理解new和make的职责分离是Go语言设计哲学的一部分,它强调了明确性和简洁性,帮助开发者更好地管理内存和编写清晰的代码。



评论(已关闭)

评论已关闭