Go语言中new和make的区别

本文深入探讨Go语言中new和make的使用方法与区别。new用于为任意类型分配内存并返回指向该内存的指针,同时将内存置零。make则专门用于初始化slice、map和channel,返回类型本身而非指针,且不置零值。了解这些差异有助于正确使用Go语言进行高效编程。

转载于:https://www.flysnow.org/2017/10/23/go-new-vs-make.html

Go语言中new和make是内建的两个函数,主要用来创建分配类型内存。在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的规则很简单,下面我们就通过一些示例说明他们的区别和使用。

变量的声明

var i int
var s string

变量的声明我们可以通过var关键字,然后就可以在程序中使用。当我们不指定变量的默认值时,这些变量的默认值是他们的零值,比如int类型的零值是0,string类型的零值是"",引用类型的零值是nil

对于例子中的两种类型的声明,我们可以直接使用,对其进行赋值输出。但是如果我们换成引用类型呢?

package main

import (
	"fmt"
)

func main() {
	var i *int
	*i=10
	fmt.Println(*i)

}

这个例子会打印出什么?0还是10?。以上全错,运行的时候会painc,原因如下:

panic: runtime error: invalid memory address or nil pointer dereference

从这个提示中可以看出,对于引用类型的变量,我们不光要声明它,还要为它分配内容空间,否则我们的值放在哪里去呢?这就是上面错误提示的原因。

对于值类型的声明不需要,是因为已经默认帮我们分配好了。

要分配内存,就引出来今天的newmake

new

对于上面的问题我们如何解决呢?既然我们知道了没有为其分配内存,那么我们使用new分配一个吧。

func main() {
	var i *int
	i=new(int)
	*i=10
	fmt.Println(*i)

}

现在再运行程序,完美PASS,打印10。现在让我们看下new这个内置的函数。

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

它只接受一个参数,这个参数是一个类型,分配好内存后,返回一个指向该类型内存地址的指针。同时请注意它同时把分配的内存置为零,也就是类型的零值。

我们的例子中,如果没有*i=10,那么打印的就是0。这里体现不出来new函数这种内存置为零的好处,我们再看一个例子。

func main() {
	u:=new(user)
	u.lock.Lock()
	u.name = "张三"
	u.lock.Unlock()

	fmt.Println(u)

}

type user struct {
	lock sync.Mutex
	name string
	age int
}

示例中的user类型中的lock字段我不用初始化,直接可以拿来用,不会有无效内存引用异常,因为它已经被零值了。

这就是new,它返回的永远是类型的指针,指向分配类型的内存地址。

make

make也是用于内存分配的,但是和new不同,它只用于chanmap以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。

注意,因为这三种类型是引用类型,所以必须得初始化,但是不是置为零值,这个和new是不一样的。

func make(t Type, size ...IntegerType) Type

从函数声明中可以看到,返回的还是该类型。

二者异同

所以从这里可以看的很明白了,二者都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。所以在我们编写程序的时候,就可以根据自己的需要很好的选择了。

make返回的还是这三个引用类型本身;而new返回的是指向类型的指针。

其实new不常用

所以有new这个内置函数,可以给我们分配一块内存让我们使用,但是现实的编码中,它是不常用的。我们通常都是采用短语句声明以及结构体的字面量达到我们的目的,比如:

i:=0
u:=user{}

这样更简洁方便,而且不会涉及到指针这种比麻烦的操作。

make函数是无可替代的,我们在使用slice、map以及channel的时候,还是要使用make进行初始化,然后才才可以对他们进行操作。

<think>好的,我需要回答用户关于Go语言makenew区别的问题。首先,用户已经提供了一些站内引用,我需要参考这些资料,同时结合之前的回答来组织内容。 用户之前的问题是关于rune类型,现在转向了makenew区别,所以我需要确保回答结构清晰,符合用户之前设定的格式要求,比如使用中文、正确LaTeX格式、相关问题生成等。 首先,我得明确makenew的基本区别。根据引用[2]引用[5],make用于slice、map、channel的初始化并分配内存,返回的是类型本身;而new用于任何类型的内存分配,返回指针,且初始化为零值。需要详细说明两者的使用场景区别。 接下来,我需要整理两者的对比表格,类似之前回答中的runebyte的对比。同时,要给出代码示例,比如引用[3]引用[4]中的例子,说明newmake的不同用法。 还需要注意性能或使用时的注意事项,比如多次使用newmake时的效率问题,或者错误使用的例子。比如引用[3]中提到使用new创建切片后再make的情况,可能不如直接使用make高效。 此外,用户之前的回答中有技术特性、核心用途、使用场景建议等部分,我需要将makenew区别按照类似的逻辑进行分点说明,比如定义与用途、返回值、适用类型、初始化行为等。 最后,生成相关问题,确保问题覆盖用户可能关心的其他方面,比如如何选择使用哪个函数,或者常见错误等。同时,确保引用标记正确,根据提供的引用来源标注[^1]到[^5]。 需要检查LaTeX语法是否正确,比如行内公式用$...$,独立公式用$$...$$。例如,在讨论内存分配时,可以提到make初始化的结构,而new返回指针,这里可能需要数学表达式来强调内存分配的不同。 可能还需要提及类型安全或错误处理的情况,比如错误使用new代替make会导致什么问题,如引用[3]中的例子,var p *[]int = new([]int)后*p是nil,需要再make才能使用,这在实际中不推荐。 总结回答结构,先概述,再分点详细对比,代码示例,使用场景,注意事项,最后相关问题。确保每个部分都有引用支持,并正确标注来源。</think>### Go语言 `make` 与 `new` 的区别深度解析 --- #### 一、核心定义与作用 1. **`new` 函数** - **功能**:为**任意类型**分配内存,返回**类型指针**(`*T`) - **行为特点**: - 仅分配内存,不初始化(填充零值)[^2][^3] - 适用于值类型(如结构体、基本类型)引用类型(但需二次初始化) - 语法示例: ```go p := new(int) // *int 类型指针,值为0 s := new([]int) // *[]int 类型指针,底层切片为nil ``` 2. **`make` 函数** - **功能**:为**特定引用类型**(`slice`、`map`、`channel`)分配并初始化内存,返回**类型实例**(`T`) - **行为特点**: - 同时完成内存分配数据结构初始化[^2][^5] - 仅适用于三种内置引用类型 - 语法示例: ```go sl := make([]int, 5) // 初始化长度为5的切片 m := make(map[string]int) // 初始化空的map ch := make(chan int) // 初始化无缓冲通道 ``` --- #### 二、关键差异对比 | 特性 | `new` | `make` | |---------------------|--------------------------------|---------------------------------| | **返回值类型** | 指针(`*T`) | 实例(`T`) | | **适用类型** | 所有类型 | 仅 `slice`、`map`、`channel` | | **初始化行为** | 零值填充(如 `nil`、`0`) | 完整初始化数据结构 | | **内存分配位置** | 堆内存 | 堆内存(但通过运行时优化管理) | | **典型使用场景** | 创建值类型指针 | 创建引用类型实例 | --- #### 三、代码实例解析 1. **`new` 的陷阱与正确用法** ```go // 错误示例:直接使用new创建切片 p := new([]int) *p = append(*p, 1) // 运行时panic(底层数组未分配) // 正确用法:new+显式初始化 p := new([]int) *p = make([]int, 0, 10) // 需要二次初始化[^3] ``` 2. **`make` 的初始化优势** ```go // 直接创建可用的切片 s := make([]int, 3, 5) // len=3, cap=5, 元素初始化为0 m := make(map[string]int) // 空map可立即操作 m["key"] = 1 // 安全操作 ``` 3. **内存布局验证** ```go // new返回指针的验证 p := new(int) fmt.Printf("%T, %v\n", p, *p) // 输出: *int, 0 // make返回实例的验证 s := make([]int, 2) fmt.Printf("%T, %v\n", s, s) // 输出: []int, [0 0] ``` --- #### 四、底层实现差异 1. **`new` 的编译过程** - 调用 `runtime.newobject` 函数分配内存 - 返回指针的数学表达: $$ \text{指针地址} = \text{堆内存分配}( \text{sizeof}(T) ) $$ 2. **`make` 的运行时逻辑** - 对不同类型的特殊处理(以切片为例): ```go // 伪代码表示切片make的底层操作 func makeslice(et *_type, len, cap int) unsafe.Pointer { mem := mallocgc(et.size*uintptr(cap), et, true) return mem } ``` - 直接构造完整数据结构(如map的哈希表桶、channel的环形缓冲区) --- #### 五、使用场景建议 | 场景类型 | 推荐方案 | 示例 | |-------------------------|-----------------|-------------------------------| | 创建值类型指针 | `new` | 结构体指针传递 | | 初始化切片/映射/通道 | `make` | 并发编程中的channel创建 | | 零值可用的类型 | `new` | `sync.Mutex` 指针初始化 | | 需要预设容量的数据结构 | `make` | 高性能预分配切片 | --- #### 六、常见错误与优化 1. **错误混用示例** ```go // 尝试用new创建map(不可直接操作) m := new(map[string]int) (*m)["key"] = 1 // panic: assignment to nil map // 正确方式 m := make(map[string]int) m["key"] = 1 ``` 2. **性能优化技巧** - **预分配容量**减少扩容开销: ```go // 使用make预设切片容量 s := make([]int, 0, 1000) // 避免append时多次扩容 ``` - **避免不必要的指针解引用**: ```go // 低效写法 p := new(struct{ x int }) (*p).x = 10 // 高效写法(直接使用值类型) var v struct{ x int } v.x = 10 ``` --- ### 关键结论 1. `new` 返回指针且不初始化数据结构,适合值类型指针创建[^2][^4] 2. `make` 直接返回初始化后的引用类型实例,是 `slice/map/channel` 的标准创建方式[^3][^5] 3. 混用两者会导致冗余操作(如 `new` 后接 `make`),应优先使用类型匹配的函数[^3] --- ### 相关问题 1. 为什么 `make` 不能用于创建结构体实例? 2. 如何选择 `new` 直接变量声明(如 `var v T`)? 3. `make` 初始化切片时,`len` `cap` 参数有什么作用? 4. 在并发编程中,`new` `make` 的使用有哪些注意事项? [^1]: 基础内存分配概念 [^2]: 核心区别总结 [^3]: 实际应用示例 [^4]: 类型返回值验证 : 特定类型初始化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值