17|复合数据类型:用结构体建立对真实世界的抽象
- 深入了解
- 翻译
- 解释
- 总结
本文深入介绍了Go语言中的结构体类型和自定义新类型的方法。通过结构体类型,可以将多个属性聚合成一个实体对象,更好地模拟和处理真实世界的问题。文章详细介绍了自定义新类型的两种方法:类型定义和类型别名,以及它们的定义方式和使用场景。此外,还探讨了结构体类型的定义方式,包括空结构体、嵌入字段等特殊情况,以及结构体变量的声明与初始化。读者可以通过本文快速了解Go语言中结构体类型的基本概念和使用方法,为进一步学习和应用提供了基础知识。文章内容深入浅出,适合对Go语言感兴趣的读者阅读学习。
《Tony Bai · Go 语言第一课》,新⼈⾸单¥59
全部留言(43)
- 最新
- 精选
- Darren一个类型,它所占用的大小是固定的,因此一个结构体定义好的时候,其大小是固定的。 但是,如果结构体里面套结构体,那么在计算该结构体占用大小的时候,就会成死循环。 但如果是指针、切片、map等类型,其本质都是一个int大小(指针,4字节或者8字节,与操作系统有关),因此该结构体的大小是固定的,记得老师前几节课讲类型的时候说过,类型就能决定内存占用的大小。 因此,结构体是可以接口自身类型的指针类型、以自身类型为元素类型的切片类型,以及以自身类型作为 value 类型的 map 类型的字段,而自己本身不行。
作者回复: 正确✅
2021-11-19999 - 西红柿牛腩泡饼因为指针、map、切片的变量元数据的内存占用大小是固定的。
作者回复: 一语点题,直中要害!
2021-11-1943 - lesserrorTony 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-2167 - 功夫熊猫因为指针的值是变量的地址,而变量的地址是一种新的数据类型。
作者回复: 不错!不过还差那么一点点:所有类型的指针的大小都是固定长度的。所以编译器可以得到这个指针类型的大小。即便在不知道T大小的情况下也可以。
2021-11-195 - 鹏如果不需要照顾 “按字段顺序对一个结构体类型变量进行显式初始化” 这种写法,是不是编译器就可以自动做内存对齐优化,即把 `type T struct { b byte i int64 u uint16}` 实质用 `type S struct { b byte u uint16 i int64}` 编译。
作者回复: 编译器不会改变字段顺序的,只会基于现有次序做缝隙填充与结构体尾部padding,保证各个字段以及整个结构体都是对齐的。
2021-11-194 - zzwdream关于内存对齐,基于字段的字节数做升序排序,是否就可以做到最优解? 内存的浪费主要是在于填充的冗余,那么可以基于字节数升序,相邻字段的字节数相同,那么就不存在填充;相邻字段的字节数不同,那么又不会因因为字节数差距太大而填充太多。 (比如相邻的字段是 byte和 uint16 ,那么只需要填充一个字节;但是相邻的字段是 byte 和 int,那么就要填充7个字节。)
作者回复: 是否是最优还不确认,但这是一种降低struct内存占用的技巧。github有一些struct布局优化的项目,可以探索一下它们使用的算法。
2022-04-163 - DullBird不知道循环定义是因为初始化的时候需要开辟内存空间。如果是循环变量的依赖的话。内存初始化就无穷无尽了。但是如果是指针,切片,map等。只需要开辟一个引用内存地址就可以了
作者回复: 思路正确。
2021-11-203 - liaomarstype T struct { t T ... ...} 这种方式,t T是一个新的自定义数据类型了, 而可以接受 指针,切片这些,因为本质上还是指向底层数据是一样的,不知道这样理解对不对。
作者回复: go是静态语言,对于一个类型,编译器要知道它的大小。如果在T类型的定义中嵌套T,那么编译器无法知道其大小。但如果是*T或[]T,编译器只需要知道指针大小以及切片这个“描述符”的大小即可。而指针与切片的大小都是固定的,对编译器来说是已知的。
2021-11-193 - 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-0922