在 Go 语言里,new(T) 和 &T{} 都能拿到类型为 *T 的指针,但它们在语义、编译行为和适用面上并不等价。很多初学者因为都能“造出一个结构体指针”而随意互换,结果在初始化逻辑和性能上踩了坑。本文从语言规范、底层机制和实际编码三个角度把这件事讲透。

一、语言层面的定义差异
根据 Go 语言规范,new(T) 是一个内置函数,它为一个类型为 T 的新项分配零值内存,并返回指向它的指针,即 *T。这里强调的是“零值”和“分配”,并不接受任何初始化参数。与之相对,&T{} 是取复合字面量地址的写法,T{} 本身是一个结构体(或数组等)的复合字面量,可以在花括号内写入字段的键值对来指定初始状态,然后 & 运算符取得它的地址。
这意味着 new(T) 永远只能给你一个全零的结构体指针,而 &T{} 既能写成全零形式 &T{}(等价于 new 的结果),也能写成 &T{Name: "test", Age: 1} 这样的部分赋值形式。从类型系统看二者返回的都是 *T,但后者在源码表达力上明显更强。
1.1 零值等价但写法不同
当不指定任何字段时,下面两行在运行结果上是一致的:
package main
import "fmt"
type User struct {
Name string
Age int
}
func main() {
a := new(User)
b := &User{}
fmt.Println(a == b) // false,不是同一个地址
fmt.Printf("%+vn", *a) // {Name: Age:0}
fmt.Printf("%+vn", *b) // {Name: Age:0}
}
虽然打印出的字段值相同,但 a 和 b 指向不同的内存块。这说明二者都是独立分配,只是初始化内容恰好都是零值。
1.2 带字段初始化的唯一途径
如果要在创建时就赋值,只能使用复合字面量:
u := &User{
Name: "alice",
Age: 30,
}
// 下面这行无法通过编译
// v := new(User{Name: "bob"}) // new 不接受参数
因此从语法限制上,new 不具备字段初始化能力,这是二者最直白的区别。
二、底层分配与逃逸分析
从编译器实现看,new(T) 在中间代码阶段通常被翻译成对 runtime.newobject 的调用,该函数从堆上申请 size 为 sizeof(T) 的内存并清零。而 &T{} 的处理更灵活:如果编译器通过逃逸分析判定该结构体在函数返回后不再被外部引用,就可以直接在栈上构造后再取地址;若发生逃逸,则同样走到堆分配。
2.1 逃逸对比示例
观察下面两段代码,前者用 new,后者用 &T{},在闭包中返回指针都会逃逸,但栈上临时使用的场景差异明显:
func useNew() *int {
p := new(int)
*p = 10
return p // 逃逸到堆
}
func useLit() *int {
x := 5
return &x // 同样逃逸到堆
}
如果改为函数内使用不返回,&T{} 可能完全留在栈帧,而 new 的语义更偏向“总是需要一块新内存”,部分旧版本编译器对其优化不如字面量积极。
2.2 性能影响
在高频创建小对象的循环中,栈分配比堆分配少一次内存管理开销。虽然 Go 的逃逸分析已经很成熟,但显式用 &T{} 并控制生命周期,比一律 new 更容易让编译器做出栈分配决策。不过对大多数业务代码,这点差异不是瓶颈,可读性才是首要考量。
三、典型使用场景建议
选哪一个,核心看“是否需要指定初始字段”和“代码表达是否清晰”。下面列出常见情形。
3.1 适合 new(T) 的场景
- 泛型函数中需要任意类型的零值指针,例如
func reset[T any]() *T { return new(T) } - 单元测试里构造一个空白依赖桩,后续由测试框架填充
- 只关心“有一块干净内存”,如实现链表节点池时
node := new(ListNode)
在这些情况下,new 的语义最短,读者一眼就知道它是零值。若硬写 &T{} 虽然没错,但略显啰嗦。
3.2 适合 &T{} 的场景
- API 请求参数构造:
req := &SearchReq{Key: "go", Page: 1} - 嵌套结构体一次性初始化,避免先 new 再逐个赋值
- 配置对象、选项模式里返回具体实例指针
复合字面量允许只写部分字段,未写的自动为零值,这种“声明即初始化”的风格让数据结构自解释,减少后续漏赋值导致的 bug。
3.3 误用带来的问题
有人为了统一风格在所有地方写 new(T),然后在后面跟一堆 p.Field = x,代码变长且容易在中间忘记赋值。也有人把 &T{} 当 new 用,却不知花括号里多写逗号不会影响,反而让新人误以为有隐藏字段。理解本质后,按场景选最贴切的写法即可。
四、总结对照表
| 对比项 | new(T) | &T{} |
|---|---|---|
| 是否接受初始化参数 | 否 | 是 |
| 返回内容 | 指向零值的 *T | 指向字面量实例的 *T |
| 分配位置 | 通常堆(newobject) | 栈或堆(逃逸分析决定) |
| 代码简洁度(零值) | 高 | 中 |
| 字段赋值表达力 | 无 | 强 |
一句话概括:new(T) 是“要一块 T 的零值内存”,&T{} 是“按我写的样子造一个 T 再取地址”。弄清这层关系,就能在 Go 项目里写得既准确又清爽。
Gonew_Tstruct_initialization修改时间:2026-08-03 04:24:33