乐平网站,网页设计心德体会500字范文,小程序开发平台哪家公司好,在线甜品网站开发书Go 复合数据类型之结构体与自定义类型 文章目录 Go 复合数据类型之结构体与自定义类型一、类型别名和自定义类型1.1 类型定义#xff08;Type Definition#xff09;简单示例 1.2 类型别名简单示例 1.3 类型定义和类型别名的区别 二、结构体2.1 结构体介绍2.2 结构体的定义2.…Go 复合数据类型之结构体与自定义类型 文章目录 Go 复合数据类型之结构体与自定义类型一、类型别名和自定义类型1.1 类型定义Type Definition简单示例 1.2 类型别名简单示例 1.3 类型定义和类型别名的区别 二、结构体2.1 结构体介绍2.2 结构体的定义2.3 定义一个空结构体2.3.1 空结构体介绍2.3.2 空结构体作用 2.4 使用其他结构体作为自定义结构体中字段的类型2.4.1 结构体嵌套介绍2.4.2 结构体嵌套的好处 2.5 匿名结构体 三、结构体变量的声明与初始化3.1 结构体变量的声明3.2 零值初始化3.3 使用顺序复合字面值初始化3.4 使用键值对初始化3.5 使用特定的构造函数 四、结构体类型的内存布局五、结构体字段的可见性六、结构体的“继承” 一、类型别名和自定义类型
在Go中可以通过使用关键字type来自定义一个新类型有两种常见的方法类型定义Type Definition和类型别名Type Alias。
1.1 类型定义Type Definition
类型定义是通过将一个已有的类型声明为一个新的类型的方式可以将一个现有类型的特性和方法继承给新类型。这个新类型具有原有类型的所有方法和属性但它们是不同的类型。
// 使用类型定义创建新类型
type MyInt intMyInt就是一种新的类型它具有int的特性。
如果一个新类型是基于某个 Go 原生类型定义的那么我们就叫 Go 原生类型为新类型的底层类型Underlying Type)它被用来判断两个类型本质上是否相同Identical。比如上面这个例子中类型 int 就是类型 MyInt 的底层类型。
简单示例
这里我们定义两个类型,新类型 T1 是基于 Go 原生类型 int 定义的新自定义类型而新类型 T2 则是基于刚刚定义的类型 T1定义的新类型:
type T1 int
type T2 T1 在上面例子中虽然 T1 和 T2 是不同类型但因为它们的底层类型都是类型 int所以它们在本质上是相同的。而本质上相同的两个类型它们的变量可以通过显式转型进行相互赋值相反如果本质上是不同的两个类型它们的变量间连显式转型都不可能更不要说相互赋值了。
比如下面这个代码示例
type T1 int
type T2 T1
type T3 stringfunc main() {var n1 T1var n2 T2 5n1 T1(n2) // okvar s T3 hellon1 T1(s) // 错误cannot convert s (type T3) to type T1
}这段代码中T1 和 T2 本质上是相同的类型所以我们可以将 T2 变量 n2 的值通过显式转型赋值给 T1 类型变量 n1。而类型 T3 的底层类型为类型 string与 T1/T2 的底层类型不同所以它们本质上就不是相同的类型。这个时候如果我们把 T3 类型变量 s 赋值给 T1 类型变量 n1编译器就会给出编译错误的提示。
1.2 类型别名
类型别名是在已有类型的基础上创建一个新类型它们是同一类型的两个名称所以它们可以互相赋值它们只是为了提供更清晰的语义或用于解决包之间的命名冲突。
类型别名规定TypeAlias只是Type的别名本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名上学后用学名英语老师又会给他起英文名但这些名字都指的是他本人。
type TypeAlias Type简单示例
type T string var s string hello
var t T s // ok
fmt.Printf(%T\n, t) // string因为类型 T 是通过类型别名的方式定义的T 与 string 实际上是一个类型所以这里使用 string 类型变量 s 给 T 类型变量 t 赋值的动作实质上就是同类型赋值。另外我们也可以看到通过 Printf 输出的变量 t 的类型信息也是 string
1.3 类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异我们通过下面的这段代码来理解它们之间的区别。
//类型定义
type NewInt int//类型别名
type MyInt intfunc main() {var a NewIntvar b MyIntfmt.Printf(type of a:%T\n, a) //type of a:main.NewIntfmt.Printf(type of b:%T\n, b) //type of b:int
}结果显示a的类型是main.NewInt表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在编译完成时并不会有MyInt类型。
二、结构体
2.1 结构体介绍
结构体是一种自定义的复合数据类型用于组合不同类型的值以创建更复杂的数据结构。每个结构体可以包含一个或多个字段每个字段都有自己的名称和类型。可以封装多个基本数据类型。结构体的字段可以是基本数据类型也可以是其他自定义结构体类型。英文名称struct。 也就是我们可以通过struct关键字来定义结构体类型。Go语言中通过struct来实现面向对象。
2.2 结构体的定义
使用type和struct关键字来定义结构体通常遵循以下格式
type T struct {Field1 T1Field2 T2... ...FieldN Tn
}其中
T为类型名标识自定义结构体的名称在同一个包内不能重复。Field1、Field2为字段名表示结构体字段名。结构体中的字段名必须唯一。T1、T2 为字段类型表示结构体字段的具体类型。
根据上面的这个定义我们会得到一个名为 T 的结构体类型定义中 struct 关键字后面的大括号包裹的内容就是一个类型字面值。我们看到这个类型字面值由若干个字段field聚合而成每个字段有自己的名字与类型并且在一个结构体中每个字段的名字应该都是唯一的。
比如这里我就用对现实世界的书Book进行抽象定义了一个结构体
type Book struct {Title string // 书名Pages int // 书的页数Indexes int // 书的索引
}这样我们就拥有了一个Book的结构体类型它有Title、Pages、Indexes三个字段分别表示书名、页数和索引。这样我们使用这个Book结构体就能够很方便的在程序中表示和存储书信息了。 var book Bookbook.Title The Go Programming Languagebook.Pages 800我们通过.来访问结构体的字段成员变量,例如book.Title和book.Pages等。
2.3 定义一个空结构体
2.3.1 空结构体介绍
空结构体**也就是没有包含任何字段的结构体类型**就像下面示例代码这样
type Empty struct{} // Empty是一个不包含任何字段的空结构体类型2.3.2 空结构体作用
空结构体类型在Go语言中有一些特殊的用途尽管它们不包含任何字段但由于其特殊性质可以用于以下几种情况
占位符空结构体可以用作占位符表示某个位置需要一个值但实际上并不需要存储任何数据。这在某些数据结构中很有用例如用于实现集合set或映射map的数据结构你可能只关心元素的存在而不关心元素的值。使用空结构体可以在占用很少内存的同时实现这一目标。通道信号在并发编程中空结构体常常用于通道channel的信号传递。你可以在通道中发送一个空结构体来表示某种信号而不必发送任何实际的数据。这在同步和通信方面非常有用。数据占位符有时候某些数据结构需要占据某个位置但不需要实际的数据此时可以使用空结构体。函数参数如果一个函数需要传递一个不需要使用的参数你可以将其定义为空结构体类型以避免引入不必要的数据。
尽管空结构体类型没有实际的数据存储但由于其在内存中的大小为0字节因此使用空结构体可以节省内存特别是在需要大量实例的情况下这种节省可以显著减少内存开销。
我们来看下面示例代码这样
var s Empty
println(unsafe.Sizeof(s)) // 0我们看到输出的空结构体类型变量的大小为 0也就是说空结构体类型变量的内存占用为 0。基于空结构体类型内存零开销这样的特性我们在日常 Go 开发中会经常使用空结构体类型元素作为一种“事件”信息进行 Goroutine 之间的通信就像下面示例代码这样
var c make(chan Empty) // 声明一个元素类型为Empty的channel
c-Empty{} // 向channel写入一个“事件”这种以空结构体为元素类建立的 channel是目前能实现的、内存占用最小的 Goroutine 间通信方式。
2.4 使用其他结构体作为自定义结构体中字段的类型
2.4.1 结构体嵌套介绍
结构体嵌套是指在一个结构体内部嵌入另一个结构体将一个结构体类型的字段嵌套到另一个结构体中以创建复合结构体。
比如这段代码这里结构体类型 Book 的字段 Author 的类型就是另外一个结构体类型 Person
type Person struct {Name stringPhone stringAddr string
}type Book struct {Title stringAuthor Person... ...
}如果我们要访问 Book 结构体字段 Author 中的 Phone 字段我们可以这样操作
var book Book
println(book.Author.Phone)同时Go 还提供了一种更为简便的定义方法那就是我们可以无需提供字段的名字只需要使用其类型就可以了以上面的 Book 结构体定义为例我们可以用下面的方式提供一个等价的定义
type Book struct {Title stringPerson... ...
}以这种方式定义的结构体字段我们叫做嵌入字段Embedded Field。我们也可以将这种字段称为匿名字段或者把类型名看作是这个字段的名字。如果我们要访问 Person 中的 Phone 字段我们可以通过下面两种方式进行
var book Book
println(book.Person.Phone) // 将类型名当作嵌入字段的名字
println(book.Phone) // 支持直接访问嵌入字段所属类型中字段第一种方式显然是通过把类型名当作嵌入字段的名字来进行操作的而第二种方式更像是一种“语法糖”我们可以“绕过”Person 类型这一层直接访问 Person 中的字段。
Go 语言不支持这种在结构体类型定义中递归地放入其自身类型字段的定义方式。面。比如下面这段代码:
type T struct {t T ... ...
}面对上面的示例代码编译器就会给出“invalid recursive type T”的错误信息。
不过虽然我们不能在结构体类型 T 定义中拥有以自身类型 T 定义的字段但是你可以在结构体中包含自身类型的指针、切片和map类型的字段这是允许的。这种方式通常用于构建递归数据结构或树状结构。比如这样
type T struct {t *T // okst []T // okm map[string]T // ok
} 这是因为指针、切片和map这些类型并没有真正包含自身类型T,只是间接引用了结构体类型 T,所以不会造成递归定义的问题。
2.4.2 结构体嵌套的好处
使用其他结构体作为字段类型的好处包括
模块化将数据模型分解成更小的结构体每个结构体关注特定的数据和功能有助于提高代码的模块化性。可读性结构体嵌套提供了更自然的数据组织方式使代码更容易阅读和理解。代码复用可以重复使用现有的结构体避免在不同地方定义相同的字段。扩展性你可以轻松地添加、修改或扩展数据模型而无需对所有相关代码进行大规模更改。
2.5 匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package mainimport (fmt
)func main() {var user struct{Name string; Age int}user.Name Jarvisuser.Age 18fmt.Printf(%#v\n, user)
}三、结构体变量的声明与初始化
3.1 结构体变量的声明
只有当结构体实例化时才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
可以使用标准变量声明语句或者是短变量声明语句声明一个结构体类型的变量
type Book struct {...
}var book Book // 标准变量声明
var book Book{}
book : Book{} //短变量声明3.2 零值初始化
零值初始化说的是使用结构体的零值作为它的初始值。“零值”指的是一个类型的默认值。对于 Go 原生类型来说这个默认值也称为零值。Go 结构体类型由若干个字段组成当这个结构体类型变量的各个字段的值都是零值时我们就说这个结构体类型变量处于零值状态。
**结构体类型的变量通常都要被赋予适当的初始值后才会有合理的意义。**通常不具有或者很难具有合理的意义比如通过下面代码得到的零值 book 变量就是这样
var book Book // book为零值结构体变量想象一下一本书既没有书名也没有作者、页数、索引等信息那么通过 Book 类型对这本书的抽象就失去了实际价值。所以对于像 Book 这样的结构体类型使用零值初始化并不是正确的选择。
那么采用零值初始化的零值结构体变量就真的没有任何价值了吗恰恰相反。如果一种类型采用零值初始化得到的零值变量是有意义的而且是直接可用的这种类型为“零值可用”类型。可以说定义零值可用类型是简化代码、改善开发者使用体验的一种重要的手段。
在 Go 语言标准库和运行时的代码中有很多践行“零值可用”理念的好例子最典型的莫过于 sync 包的 Mutex 类型了。Mutex 是 Go 标准库中提供的、用于多个并发 Goroutine 之间进行同步的互斥锁。我们来看下下面这段代码:
var mu sync.Mutex
mu.Lock()
mu.Unlock()Go 标准库的设计者很贴心地将 sync.Mutex 结构体的零值状态设计为可用状态这样开发者便可直接基于零值状态下的 Mutex 进行 lock 与 unlock 操作而且不需要额外显式地对它进行初始化操作了。
Go 标准库中的 bytes.Buffer 结构体类型也是一个零值可用类型的典型例子这里我演示了 bytes.Buffer 类型的常规用法
var b bytes.Buffer
b.Write([]byte(Hello, Go))
fmt.Println(b.String()) // 输出Hello, Go可以看到我们不需要对 bytes.Buffer 类型的变量 b 进行任何显式初始化就可以直接通过处于零值状态的变量 b调用它的方法进行写入和读取操作。
3.3 使用顺序复合字面值初始化
对于我们前面的 Book 类型确实不能设计为零值可用类型,因为它们的零值并非有效值。对于这类类型我们需要对它的变量进行显式的初始化后才能正确使用。在日常开发中对结构体类型变量进行显式初始化的最常用方法就是使用复合字面值。
最简单的对结构体变量进行显式初始化的方式就是按顺序依次给每个结构体字段进行赋值比如下面的代码 type Book struct {Title string // 书名Pages int // 书的页数Indexes int // 书的索引}var book Book{The Go Programming Language, 700, 9}我们依然可以用这种方法给结构体的每一个字段依次赋值但这种方法也有很多问题
当结构体类型定义中的字段顺序发生变化或者字段出现增删操作时我们就需要手动调整该结构体类型变量的显式初始化代码让赋值顺序与调整后的字段顺序一致。当一个结构体的字段较多时这种逐一字段赋值的方式实施起来就会比较困难而且容易出错开发人员需要来回对照结构体类型中字段的类型与顺序谨慎编写字面值表达式。一旦结构体中包含非导出字段那么这种逐一字段赋值的方式就不再被支持了编译器会报错
type T struct {F1 intF2 stringf3 intF4 intF5 int
}var t T{11, hello, 13} // 错误implicit assignment of unexported field f3 in T literal
或
var t T{11, hello, 13, 14, 15} // 错误implicit assignment of unexported field f3 in T literal事实上Go语言并不推荐我们按照字段顺序对一个结构体变量进行显示初始化。而是要带上字段名甚至 Go 官方还在提供的 go vet 工具中专门内置了一条检查规则“composites”用来静态检查代码中结构体变量初始化是否使用了这种方法一旦发现就会给出警告。
那么我们应该用哪种形式的复合字面值给结构体变量赋初值呢
3.4 使用键值对初始化
使用键值对对结构体进行初始化时键对应结构体的字段值对应该字段的初始值。
Go 推荐我们用“field:value”形式的复合字面值对结构体类型变量进行显式初始化**这种方式可以降低结构体类型使用者和结构体类型设计者之间的耦合**这也是 Go 语言的惯用法。这里我们用“field:value”形式复合字面值对上面的类型 T 的变量进行初始化看看
var t T{F2: hello,F1: 11,F4: 14,
}我们看到使用这种“field:value”形式的复合字面值对结构体类型变量进行初始化非常灵活。和之前的顺序复合字面值形式相比“field:value”形式字面值中的字段可以以任意次序出现。未显式出现在字面值中的结构体字段比如上面例子中的 F5将采用它对应类型的零值。
复合字面值作为结构体类型变量初值被广泛使用即便结构体采用类型零值时我们也会使用复合字面值的形式
t : T{}而比较少使用 new 这一个 Go 预定义的函数来创建结构体变量实例
tp : new(T)这里值得我们注意的是我们不能用从其他包导入的结构体中的未导出字段来作为复合字面值中的 field。这会导致编译错误因为未导出字段是不可见的。
那么如果一个结构体类型中包含未导出字段并且这个字段的零值还不可用时我们要如何初始化这个结构体类型的变量呢又或是一个结构体类型中的某些字段需要一个复杂的初始化逻辑我们又该怎么做呢这时我们就需要使用一个特定的构造函数来创建并初始化结构体变量了。
3.5 使用特定的构造函数
通过专用构造函数进行结构体类型变量创建、初始化大多都符合这种模式
func NewT(field1, field2, ...) *T {... ...
}这里NewT 是结构体类型 T 的专用构造函数它的参数列表中的参数通常与 T 定义中的导出字段相对应返回值则是一个 T 指针类型的变量。T 的非导出字段在 NewT 内部进行初始化一些需要复杂初始化逻辑的字段也会在 NewT 内部完成初始化。这样我们只要调用 NewT 函数就可以得到一个可用的 T 指针类型变量了。
其实使用特定的构造函数创建并初始化结构体变量的例子并不罕见。在 Go 标准库中就有很多其中 time.Timer 这个结构体就是一个典型的例子它的定义如下
// $GOROOT/src/time/sleep.go
type runtimeTimer struct {pp uintptrwhen int64period int64f func(interface{}, uintptr) arg interface{}seq uintptrnextwhen int64status uint32
}type Timer struct {C -chan Timer runtimeTimer
}我们看到Timer 结构体中包含了一个非导出字段 rr 的类型为另外一个结构体类型 runtimeTimer。这个结构体更为复杂而且我们一眼就可以看出来这个 runtimeTimer 结构体不是零值可用的那我们在创建一个 Timer 类型变量时就没法使用显式复合字面值的方式了。这个时候Go 标准库提供了一个 Timer 结构体专用的构造函数 NewTimer它的实现如下
// $GOROOT/src/time/sleep.go
func NewTimer(d Duration) *Timer {c : make(chan Time, 1)t : Timer{C: c,r: runtimeTimer{when: when(d),f: sendTime,arg: c,},}startTimer(t.r)return t
}我们看到NewTimer 这个函数只接受一个表示定时时间的参数 d在经过一个复杂的初始化过程后它返回了一个处于可用状态的 Timer 类型指针实例。
四、结构体类型的内存布局
Go 结构体类型是既数组类型之后第二个将它的元素结构体字段一个接着一个以“平铺”形式存放在一个连续内存块中的。下图是一个结构体类型 T 的内存布局 我们看到结构体类型 T 在内存中布局是非常紧凑的Go 为它分配的内存都用来存储字段了没有被 Go 编译器插入的额外字段。我们可以借助标准库 unsafe 包提供的函数获得结构体类型变量占用的内存大小以及它每个字段在内存中相对于结构体变量起始地址的偏移量
var t T
unsafe.Sizeof(t) // 结构体类型变量占用的内存大小
unsafe.Offsetof(t.Fn) // 字段Fn在内存中相对于变量t起始地址的偏移量不过上面这张示意图是比较理想的状态真实的情况可能就没那么好了 在真实情况下虽然 Go 编译器没有在结构体变量占用的内存空间中插入额外字段但结构体字段实际上可能并不是紧密相连的中间可能存在“缝隙”。这些“缝隙”同样是结构体变量占用的内存空间的一部分它们是 Go 编译器插入的“填充物Padding”。
那么Go 编译器为什么要在结构体的字段间插入“填充物”呢这其实是内存对齐的要求。所谓内存对齐指的就是各种内存对象的内存地址不是随意确定的必须满足特定要求。
对于各种基本数据类型来说它的变量的内存地址值必须是其类型本身大小的整数倍比如一个 int64 类型的变量的内存地址应该能被 int64 类型自身的大小也就是 8 整除一个 uint16 类型的变量的内存地址应该能被 uint16 类型自身的大小也就是 2 整除。
这些基本数据类型的对齐要求很好理解那么像结构体类型这样的复合数据类型内存对齐又是怎么要求的呢是不是它的内存地址也必须是它类型大小的整数倍呢
实际上没有这么严格。对于结构体而言它的变量的内存地址只要是它最长字段长度与系统对齐系数两者之间较小的那个的整数倍就可以了。但对于结构体类型来说我们还要让它每个字段的内存地址都严格满足内存对齐要求。
这么说依然比较绕我们来看一个具体例子计算一下这个结构体类型 T 的对齐系数
type T struct {b bytei int64u uint16
}计算过程是这样的 我们简单分析一下整个计算过程分为两个阶段。第一个阶段是对齐结构体的各个字段。
首先我们看第一个字段 b 是长度 1 个字节的 byte 类型变量这样字段 b 放在任意地址上都可以被 1 整除所以我们说它是天生对齐的。我们用一个 sum 来表示当前已经对齐的内存空间的大小这个时候 sum1
接下来我们看第二个字段 i它是一个长度为 8 个字节的 int64 类型变量。按照内存对齐要求它应该被放在可以被 8 整除的地址上。但是如果把 i 紧邻 b 进行分配当 i 的地址可以被 8 整除时b 的地址就无法被 8 整除。这个时候我们需要在 b 与 i 之间做一些填充使得 i 的地址可以被 8 整除时b 的地址也始终可以被 8 整除于是我们在 i 与 b 之间填充了 7 个字节此时此刻 sum178
再下来我们看第三个字段 u它是一个长度为 2 个字节的 uint16 类型变量按照内存对其要求它应该被放在可以被 2 整除的地址上。有了对其的 i 作为基础我们现在知道将 u 与 i 相邻而放是可以满足其地址的对齐要求的。i 之后的那个字节的地址肯定可以被 8 整除也一定可以被 2 整除。于是我们把 u 直接放在 i 的后面中间不需要填充此时此刻sum1782。
现在结构体 T 的所有字段都已经对齐了我们开始第二个阶段也就是对齐整个结构体。
我们前面提到过结构体的内存地址为 min结构体最长字段的长度系统内存对齐系数的整数倍那么这里结构体 T 最长字段为 i它的长度为 8而 64bit 系统上的系统内存对齐系数一般为 8两者相同我们取 8 就可以了。那么整个结构体的对齐系数就是 8。
这个时候问题就来了为什么上面的示意图还要在结构体的尾部填充了 6 个字节呢
我们说过结构体 T 的对齐系数是 8那么我们就要保证每个结构体 T 的变量的内存地址都能被 8 整除。如果我们只分配一个 T 类型变量不再继续填充也可能保证其内存地址为 8 的倍数。但如果考虑我们分配的是一个元素为 T 类型的数组比如下面这行代码我们虽然可以保证 T[0]这个元素地址可以被 8 整除但能保证 T[1]的地址也可以被 8 整除吗
var array [10]T我们知道数组是元素连续存储的一种类型元素 T[1]的地址为 T[0]地址 T 的大小 (18)显然无法被 8 整除这将导致 T[1]及后续元素的地址都无法对齐这显然不能满足内存对齐的要求。
问题的根源在哪里呢问题就在于 T 的当前大小为 18这是一个不能被 8 整除的数值如果 T 的大小可以被 8 整除那问题就解决了。于是我们才有了最后一个步骤我们从 18 开始向后找到第一个可以被 8 整除的数字也就是将 18 圆整到 8 的倍数上我们得到 24我们将 24 作为类型 T 最终的大小就可以了。
为什么会出现内存对齐的要求呢这是出于对处理器存取数据效率的考虑。在早期的一些处理器中比如 Sun 公司的 Sparc 处理器仅支持内存对齐的地址如果它遇到没有对齐的内存地址会引发段错误导致程序崩溃。我们常见的 x86-64 架构处理器虽然处理未对齐的内存地址不会出现段错误但数据的存取性能也会受到影响。
从这个推演过程中你应该已经知道了Go 语言中结构体类型的大小受内存对齐约束的影响。这样一来不同的字段排列顺序也会影响到“填充字节”的多少从而影响到整个结构体大小。比如下面两个结构体类型表示的抽象是相同的但正是因为字段排列顺序不同导致它们的大小也不同
type T struct {b bytei int64u uint16
}type S struct {b byteu uint16i int64
}func main() {var t Tprintln(unsafe.Sizeof(t)) // 24var s Sprintln(unsafe.Sizeof(s)) // 16
}所以你在日常定义结构体时一定要注意结构体中字段顺序尽量合理排序降低结构体对内存空间的占用。
另外前面例子中的内存填充部分是由编译器自动完成的。不过有些时候为了保证某个字段的内存地址有更为严格的约束我们也会做主动填充。比如 runtime 包中的 mstats 结构体定义就采用了主动填充
// $GOROOT/src/runtime/mstats.go
type mstats struct {... ...// Add an uint32 for even number of size classes to align below fields// to 64 bits for atomic operations on 32 bit platforms._ [1 - _NumSizeClasses%2]uint32 // 这里做了主动填充last_gc_nanotime uint64 // last gc (monotonic time)last_heap_inuse uint64 // heap_inuse at mark termination of the previous GC... ...
}通常我们会通过空标识符来进行主动填充因为填充的这部分内容我们并不关心。
五、结构体字段的可见性
结构体中字段大写开头表示可公开访问小写表示私有仅在定义当前结构体的包中可访问。
六、结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
//Animal 动物
type Animal struct {name string
}func (a *Animal) move() {fmt.Printf(%s会动\n, a.name)
}//Dog 狗
type Dog struct {Feet int8*Animal //通过嵌套匿名结构体实现继承
}func (d *Dog) wang() {fmt.Printf(%s会汪汪汪~\n, d.name)
}func main() {d1 : Dog{Feet: 4,Animal: Animal{ //注意嵌套的是结构体指针name: 乐乐,},}d1.wang() //乐乐会汪汪汪~d1.move() //乐乐会动
}