Tony Bai · Go 语言第一课
Tony Bai
资深架构师,tonybai.com 博主
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 59 讲
开篇词 (1讲)
结束语 (1讲)
Tony Bai · Go 语言第一课
15
15
1.0x
00:00/00:00
登录|注册

17|复合数据类型:用结构体建立对真实世界的抽象

你好,我是 Tony Bai。
在前面的几节课中,我们一直在讲数据类型,包括 Go 基本数据类型和三个复合数据类型。我们可以用这些数据类型来建立对真实世界的抽象。
那么什么是对真实世界的抽象呢?我们编写程序的目的就是与真实世界交互,解决真实世界的问题,帮助真实世界提高运行效率与改善运行质量。所以我们就需要对真实世界事物体的重要属性进行提炼,并映射到程序世界中,这就是所谓的对真实世界的抽象。
不同的数据类型具有不同的抽象能力,比如整数类型 int 可以用来抽象一个真实世界物体的长度,string 类型可以用来抽象真实世界物体的名字,等等。
但是光有这些类型的抽象能力还不够,我们还缺少一种通用的、对实体对象进行聚合抽象的能力。你可以回想一下,我们目前可以用学过的各种类型抽象出书名、书的页数以及书的索引,但有没有一种类型,可以抽象出聚合了上述属性的“书”这个实体对象呢?
有的。在 Go 中,提供这种聚合抽象能力的类型是结构体类型,也就是 struct。这一节课,我们就围绕着结构体的使用和内存表示,由外及里来学习 Go 中的结构体类型。
不过,在学习如何定义一个结构体类型之前,我们首先要来看看如何在 Go 中自定义一个新类型。有了这个基础,我们再理解结构体类型的定义方法就十分自然了。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了Go语言中的结构体类型和自定义新类型的方法。通过结构体类型,可以将多个属性聚合成一个实体对象,更好地模拟和处理真实世界的问题。文章详细介绍了自定义新类型的两种方法:类型定义和类型别名,以及它们的定义方式和使用场景。此外,还探讨了结构体类型的定义方式,包括空结构体、嵌入字段等特殊情况,以及结构体变量的声明与初始化。读者可以通过本文快速了解Go语言中结构体类型的基本概念和使用方法,为进一步学习和应用提供了基础知识。文章内容深入浅出,适合对Go语言感兴趣的读者阅读学习。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Tony Bai · Go 语言第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(43)

  • 最新
  • 精选
  • Darren
    一个类型,它所占用的大小是固定的,因此一个结构体定义好的时候,其大小是固定的。 但是,如果结构体里面套结构体,那么在计算该结构体占用大小的时候,就会成死循环。 但如果是指针、切片、map等类型,其本质都是一个int大小(指针,4字节或者8字节,与操作系统有关),因此该结构体的大小是固定的,记得老师前几节课讲类型的时候说过,类型就能决定内存占用的大小。 因此,结构体是可以接口自身类型的指针类型、以自身类型为元素类型的切片类型,以及以自身类型作为 value 类型的 map 类型的字段,而自己本身不行。

    作者回复: 正确✅

    2021-11-19
    9
    99
  • 西红柿牛腩泡饼
    因为指针、map、切片的变量元数据的内存占用大小是固定的。

    作者回复: 一语点题,直中要害!

    2021-11-19
    43
  • lesserror
    Tony Bai 老师这节课的内容很多,尤其是内存对齐这块儿的知识,让我眼前一亮。不过有几处疑惑: 1. i的地址要能被8整除,我的理解是不应该就是图中第8个格子开始计算的么? 那为什么和b之间填充了七个格子,这样i的地址就是从第9个格子开始的,1+7 之后。不应该只需填充6个格子就行了吗? 2. 文中说:“但是,如果把 i 紧邻 b 进行分配,当 i 的地址可以被 8 整除时,b 的地址就无法被 8 整除。这个时候,我们需要在 b 与 i 之间做一些填充,使得 i 的地址可以被 8 整除时,b 的地址也始终可以被 8 整除,于是我们在 i 与 b 之间填充了 7 个字节,此时此刻 sum=1+7+8;” 这里的b只要能被1整除就行了,这里怎么又和8扯上关系了? 反复读了这段话,始终没明白。 3. 文中的这段代码的错误:var t3 = T{11, "hello", 13} // 错误:implicit assignment of unexported field 'f3' in T literal 后面的错误信息是在哪里提示的,我这里运行代码和IDE给出的错误信息都是: too few values in T{...} 并没有这个错误提示:implicit assignment of unexported field 'f3' in T literal 4. 课后问题的标准答案是什么? 我看大家众说纷纭,这里的答案,我认为还是很关键的。

    作者回复: 1. 可以以一个具体例子来说明。假设b所在内存单元的地址为8,i的地址为16,那么i与b之间是7个格子还是8个格子呢?是不是应该是7个格子? 2. 这算是给后面做铺垫吧。b是结构体的第一个字段,b的地址起始就是结构体变量的地址。虽然b作为byte类型,其自身的对齐约束是1,但是考虑到整个结构体,实际上go编译器为b分配的地址必须是被8整除的。 3. 不要在一个包里用,代码前面说过:“一旦结构体中包含非导出字段,那么这种逐一字段赋值的方式就不再被支持了”。所以建立一个新包,导入T,创建T类型变量并赋值。 4. go是静态语言,对于一个类型,编译器要知道它的大小。如果嵌套T,那么编译器无法知道其大小。但如果是*T或[]T,编译器只需要知道指针大小以及切片这个“描述符”的大小即可。

    2021-11-21
    6
    7
  • 功夫熊猫
    因为指针的值是变量的地址,而变量的地址是一种新的数据类型。

    作者回复: 不错!不过还差那么一点点:所有类型的指针的大小都是固定长度的。所以编译器可以得到这个指针类型的大小。即便在不知道T大小的情况下也可以。

    2021-11-19
    5
  • 如果不需要照顾 “按字段顺序对一个结构体类型变量进行显式初始化” 这种写法,是不是编译器就可以自动做内存对齐优化,即把 `type T struct { b byte i int64 u uint16}` 实质用 `type S struct { b byte u uint16 i int64}` 编译。

    作者回复: 编译器不会改变字段顺序的,只会基于现有次序做缝隙填充与结构体尾部padding,保证各个字段以及整个结构体都是对齐的。

    2021-11-19
    4
  • zzwdream
    关于内存对齐,基于字段的字节数做升序排序,是否就可以做到最优解? 内存的浪费主要是在于填充的冗余,那么可以基于字节数升序,相邻字段的字节数相同,那么就不存在填充;相邻字段的字节数不同,那么又不会因因为字节数差距太大而填充太多。 (比如相邻的字段是 byte和 uint16 ,那么只需要填充一个字节;但是相邻的字段是 byte 和 int,那么就要填充7个字节。)

    作者回复: 是否是最优还不确认,但这是一种降低struct内存占用的技巧。github有一些struct布局优化的项目,可以探索一下它们使用的算法。

    2022-04-16
    3
  • DullBird
    不知道循环定义是因为初始化的时候需要开辟内存空间。如果是循环变量的依赖的话。内存初始化就无穷无尽了。但是如果是指针,切片,map等。只需要开辟一个引用内存地址就可以了

    作者回复: 思路正确。

    2021-11-20
    3
  • liaomars
    type T struct { t T ... ...} 这种方式,t T是一个新的自定义数据类型了, 而可以接受 指针,切片这些,因为本质上还是指向底层数据是一样的,不知道这样理解对不对。

    作者回复: go是静态语言,对于一个类型,编译器要知道它的大小。如果在T类型的定义中嵌套T,那么编译器无法知道其大小。但如果是*T或[]T,编译器只需要知道指针大小以及切片这个“描述符”的大小即可。而指针与切片的大小都是固定的,对编译器来说是已知的。

    2021-11-19
    3
  • Verson
    老师请教下,“渐进式重构”作为类型别名应用的场景,有没有具体的案例说明下

    作者回复: type alias当初加入go,主要是为一些大型代码仓库的重构提供方便。大型代码仓库重构不是一蹴而就的,需要过渡期。 在过渡期内,API迁移后,应该依旧在原先的位置上可用。比如A-Z包都依赖foo.F1,但foo.F1迁移到了bar.F1,这时如果没有alias,所有依赖foo.F1的包编译都会失败。 但在大型代码仓库中,要想很好的完成这样的重构,更多是协调工作,需要A-Z包同时将依赖foo.F1替换为bar.F1,这很困难,除非是统一行动。于是为了支持一种过渡,原foo包的开发者就希望这个foo.F1 -> bar.F1的迁移对于依赖foo.F1的包来说是透明的,即便迁移完依旧可用。那么有了type alias后,只需在foo包中添加一行 type F1 = bar.F1即可。 这样一来,就满足了过渡期的要求。大型代码仓库重构都是这样循序渐进的。

    2023-01-11归属地:广东
    2
  • 老师,麻烦问一下,结构体中使用空标识符“_”来做作为结构体字段,具体有什么用呢?

    作者回复: 占位符。让编译器分配对应的内存空间。

    2022-07-09
    2
    2
收起评论
显示
设置
留言
43
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部