Go基础:Go语言中内存分配用 new 还是 make?什么情况下该用谁?

文章目录

    • 一、基本概念对比
      • [1.1 `new` 函数](#1.1 new 函数)
      • [1.2 `make` 函数](#1.2 make 函数)
      • [1.3 详细区别对比](#1.3 详细区别对比)
      • [1.4 核心要点](#1.4 核心要点)
      • [1.5 使用场景](#1.5 使用场景)
    • 二、使用场景详解
      • [2.1 什么时候使用 `new`](#2.1 什么时候使用 new)
      • [2.2 什么时候使用 `make`](#2.2 什么时候使用 make)
    • 三、常见错误
      • [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语言中,newmake 是两个用于内存分配的内建函数,但它们有着完全不同的用途和行为。理解它们的区别对于编写高效、正确的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 核心要点

  1. new 用于分配内存并返回指针,适用于所有类型
  2. make 用于创建和初始化slice、map、channel,返回类型本身
  3. 对slice、map、channel必须使用make,不能使用new
  4. 对于结构体,优先使用字面量语法 &Type{}
  5. 理解零值初始化的概念,避免对未初始化的引用类型操作

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 返回的是类型本身,不是指针,不能直接赋值给指针变量。

相关推荐
Absinthe_苦艾酒4 小时前
golang基础语法(三)常量、指针、别名、关键字、运算符、字符串类型转换
开发语言·后端·go
MSTcheng.4 小时前
【C++】类和对象—(下) 收官之战
开发语言·c++
迪丽热爱4 小时前
C程序设计-01程序设计和C语言
c语言·开发语言
ZNineSun5 小时前
第一章:Go语言的起源-云原生时代的C位语言
云原生·golang
ん贤5 小时前
GO项目开发规范文档解读
开发语言·后端·golang
weixin_420947645 小时前
golang1.18升级到1.23遇到的坑
golang
ChineHe5 小时前
Golang语言基础篇003_数组、切片、map详解
开发语言·后端·golang
key_Go5 小时前
03.镜像
运维·服务器·网络·docker
Anthony_2315 小时前
Dockerfile构建镜像以及网络
linux·运维·服务器·网络·docker