0

0

Go 中嵌入结构体时应选择指针还是值类型?

霞舞

霞舞

发布时间:2026-01-05 13:44:03

|

891人浏览过

|

来源于php中文网

原创

Go 中嵌入结构体时应选择指针还是值类型?

go 结构体嵌入中,可嵌入值类型或其指针,但推荐优先使用指针:它支持方法提升、允许运行时动态替换底层实例、避免冗余拷贝,并天然契合 flyweight 等内存优化模式。

在 Go 中,嵌入(embedding)是实现组合与代码复用的核心机制。当你将一个类型作为匿名字段嵌入到另一个结构体中时,该类型的导出方法会被“提升”(promoted)到外层结构体上,从而实现类似继承的语义。此时一个关键问题是:*该嵌入字段应声明为值类型(如 log.Logger),还是指针类型(如 `log.Logger`)?**

答案是:*两者语法均合法,但绝大多数场景下应优先使用指针嵌入(`T`)**。原因如下:

✅ 方法提升要求兼容性

Go 规范明确允许嵌入非接口类型的指针(*T)或类型名(T),前提是 T 本身不是指针类型。log.Logger 是一个具体结构体(非接口),因此 log.Logger 和 *log.Logger 均可合法嵌入。但注意:*只有嵌入 `T时,T的所有方法(包括指针接收者方法)才能被完整提升**;若嵌入T值类型,则仅值接收者方法可用——而标准库中绝大多数类型(包括log.Logger)的方法均定义在指针接收者上(例如Logger.Printf)。嵌入log.Logger` 值会导致方法不可用,编译失败。

type Job struct {
    Command string
    *log.Logger // ✅ 正确:可调用 Logger.Printf, Logger.Println 等
}

✅ 运行时灵活性:动态共享与替换

指针嵌入赋予结构体在运行时绑定/切换底层实例的能力,这是值嵌入完全无法实现的。典型应用是 Flyweight 模式——多个逻辑对象共享同一份重型数据,仅保留轻量级引用:

type Bitmap struct {
    data [4][5]bool
}

type Renderer struct {
    *Bitmap // 嵌入指针,支持运行时赋值
    on, off byte
}

func (r *Renderer) render() {
    for _, row := range r.data {
        for _, v := range row {
            fmt.Print(string(map[bool]byte{true: r.on, false: r.off}[v]))
        }
        fmt.Println()
    }
}

// 共享同一 Bitmap 实例
var pic Bitmap
pic.data[0][0], pic.data[1][1] = true, true

renderA, renderB := Renderer{on: 'X', off: 'O'}, Renderer{on: '@', off: '.'}
renderA.Bitmap = &pic // 动态绑定
renderB.Bitmap = &pic // 同一底层数组

renderA.render() // 输出含 X/O 的图案
renderB.render() // 同一数据,不同字符映射

若改为 Bitmap 值嵌入,则每个 Renderer 实例都会拷贝整个 [4][5]bool 数组,失去共享能力,且无法后期修改所嵌入的数据源。

巧文书
巧文书

巧文书是一款AI写标书、AI写方案的产品。通过自研的先进AI大模型,精准解析招标文件,智能生成投标内容。

下载

✅ 初始化友好性

标准库和主流 Go 包普遍遵循 NewXXX() 返回 *T 的约定(如 log.New() 返回 *log.Logger)。指针嵌入可直接接收此类返回值,无需额外解引用或构造:

job := Job{
    Command: "backup",
    Logger:  log.New(os.Stdout, "[JOB] ", log.LstdFlags),
}

而值嵌入则需手动构造完整结构体,违背封装原则且易出错。

⚠️ 注意事项

  • 禁止嵌入 `T或*interface{}`**:Go 明确禁止指针到指针或指针到接口的匿名字段,因其不携带可提升的方法集。
  • 零值安全性:指针嵌入字段初始为 nil,调用其方法前需确保已初始化,否则 panic。可在构造函数中强制校验:
    func NewJob(cmd string, logger *log.Logger) *Job {
        if logger == nil {
            panic("logger must not be nil")
        }
        return &Job{Command: cmd, Logger: logger}
    }
  • 并发安全:共享指针嵌入实例时,需自行保证底层数据的并发访问安全(如加锁或使用原子操作)。

综上,除非你明确需要值语义(如嵌入小型、不可变、无指针接收者方法的结构体),否则始终优先选择 *T 进行嵌入——它更符合 Go 的惯用法,提供更强的表达力、灵活性与内存效率。

相关专题

更多
printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

72

2023.06.20

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

277

2023.11.28

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1003

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

56

2025.10.17

漫蛙2入口地址合集
漫蛙2入口地址合集

本专题整合了漫蛙2入口汇总,阅读专题下面的文章了解更多详细内容。

13

2026.01.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 3.4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号