导读:本期聚焦于小伙伴创作的《Go 语言中 new(T) 和 &T{} 到底有什么区别?该在什么场景下选择?》,敬请观看详情。刚接触 Go 的工程师常把 new(T) 和 &T{} 混为一谈,认为二者只是写法不同。其实 new(T) 始终返回指向零值的指针,而 &T{} 是复合字面量取地址,允许在初始化时赋字段值。从汇编层面看,new 调用 runtime.newobject 在堆分配并清零,&T{} 在支持逃逸分析时可能直接栈上分配。当结构体需要非零初始字段、嵌套初始化或指定部分字段时,&T{} 更直观;仅要一块干净内存、写泛型或测试桩时,new(T) 更简洁。理解二者差异能避免误用导致的隐式拷贝和可读性下降。

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

Go 语言中 new(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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。