书接上篇,咱们了解了Go语言的接口类型,如今介绍Go语言的结构体类型。主要以下:git
结构体类型既能够包含若干个命名元素(又称字段),又能够与若干个方法相关联。github
结构体类型的声明能够包含若干个字段的声明。字段声明左边的标识符表示了该字段的名称,右边的标识符表明了该字段的类型,这两个标识符之间用空格分隔。json
结构体类型声明中的每一个字段声明都独占一行。同一个结构体类型声明中的字段不能出现重名的状况。数组
结构体类型也分为命名结构体类型和匿名结构体类型。缓存
命名结构体类型ide
命名结构体类型以关键字type开始,依次包含结构体类型的名称、关键字struct和由花括号括起来的字段声明列表。以下:函数
type Sequence struct { len int cap int Sortable sortableArray sort.Interface }
结构体类型的字段的类型能够是任何数据类型。当字段名称的首字母是大写字母时,咱们就能够在任何位置(包括其余代码包)上经过其所属的结构体类型的值(如下简称结构体值)和选择表达式访问到它们。不然当字段名称的首字母是小写,这些字段就是包级私有的(只有在该结构体声明所属的代码包中才能对它们进行访问或者给它们赋值)。学习
若是一个字段声明中只有类型而没有指定名称,这个字段就叫作匿名字段。如上结构体 Sequence 中的 Sortable 就是一个匿名字段。匿名字段有时也被称为嵌入式的字段或结构体类型的嵌入类型。ui
匿名字段的类型必须由一个数据类型的名称或者一个与非接口类型对应的指针类型的名称表明。表明匿名字段类型的非限定名称将被隐含地做为该字段的名称。若是匿名字段是一个指针类型的话,那么这个指针类型所指的数据类型的非限定名称(由非限定标识符表明的名称)就会被做为该字段的名称。非限定标识符就是不包含代码包名称和点的标识符。指针
匿名类型的隐含名称的实例,以下:
type Anonymities struct { T1 *T2 P.T3 *P.T4 }
这个名为 Anonymities 的结构体类型包含了4个匿名字段。其中,T1 和 P.T3 为非指针的数据类型,它们隐含的名称分别为 T1 和 T3;*T2* 和 P.T4 为指针类型,它们隐含的名称分别为 T2 和 T4**。
注意:匿名字段的隐含名称也不能与它所属的结构体类型中的其余字段名称重复。
结构体类型中的嵌入字段的类型所附带的方法都会成为该结构体类型的方法,结构体类型自动实现了它包含的全部嵌入类型所实现的接口类型。可是嵌入类型的方法的接收者类型仍然是该嵌入类型,而不是被嵌入的结构体类型。当在结构体类型中调用实际上属于嵌入类型的方法的时候,这一调用会被自动转发到这个嵌入类型的值上。
如今对 Sequence 的声明进行改动,以下:
type Sequence struct { Sortable sorted bool }
上面的 Sequence 中的匿名字段 Sortable 用来存储和操做可排序序列,布尔类型的字段 sorted 用来表示类型值是否已经被排序。
假设有一个 Sequence 类型的值 seq,调用 Sortable 接口类型中的方法 Sort,以下:
seq.Sort()
若是 Sequence 类型中也包含了一个与 Sortable 接口类型中的方法 Sort 的名称和签名相同的方法,那么上面的调用必定是对 Sequence 类型值自身附带的 Sort 方法的调用,而嵌入类型 Sortable 的方法 Sort 被隐藏了。
若是须要在原有的排序操做上添加一些额外功能,能够这样声明一个同名的方法:
func (self *Sequence) Sort() { self.Sortable.Sort() self.sorted = true }
这样声明的方法实现了对于匿名字段 Sortable 的 Sort 方法的功能进行无缝扩展的目的。
若是两个 Sort 方法的名称相同但签名不一样,那么嵌入类型 Sortable 的方法 Sort 也一样会被隐藏。这时,在 Sequence 的类型值上调用 Sort 方法的时候,必须依据该 Sequence 结构体类型的 Sort 方法的签名来编写调用表达式。以下声明 Sequence 类型附带的名为 Sort 的方法:
func (self *Sequence) Sort(quicksort bool) { //省略若干语句 }
可是调用表达式 seq.Sort() 就会形成一个编译错误,由于 Sortable 的无参数的 Sort 方法已经被隐藏了,只能经过 seq.Sort(true) 或 seq.Sort(false) 来对 Sequence 的 Sort 方法进行调用。
注意:不管被嵌入类型是否包含了同名的方法,调用表达式 seq.Sortable.Sort() 老是能够来调用嵌入类 Sortable 的 Sort 方法。
如今,区别一下嵌入类型是一个非指针的数据类型仍是一个指针类型,假设有结构体类型 S 和非指针类型的数据类型 T,那么 *S 表示指向 S* 的指针类型,T 表示指向 T** 的指针类型,则:
若是在 S 中包含了一个嵌入类型 T,那么 S 和 *S 的方法集合中都会包含接收者类型为 T* 的方法。除此以外,S** 的方法集合中还会包含接收者类型为 *T 的方法。
如今再讨论另外一个问题。假设,咱们有一个名为 List 的结构体类型,而且在它的声明中嵌入了类型 Sequence,以下:
type List struct { Sequence }
假设有一个 List 类型的值 list,调用嵌入的 Sequence 类型值的字段 sorted,以下:
list.sorted
若是 List 类型也有一个名称为 sorted 的字段的话,那么其中的 Sequence 类型值的字段 sorted 就会被隐藏。
注意: 选择表达式 list.sorted 只表明了对 List 类型的 sorted 字段的访问,不论这两个名称为 sorted 的字段的类型是否相同。和上面的相似,这里选择表达式 list.Sequence.sorted 老是能够访问到嵌入类型 Sequence 的值的 sorted 字段。
对于结构体类型的多层嵌入的规则,有两点须要说明:
能够在被嵌入的结构体类型的值上像调用它本身的字段或方法那样调用任意深度的嵌入类型值的字段或方法。惟一的前提条件就是这些嵌入类型的字段或方法没有被隐藏。若是它们被隐藏,也能够经过相似 list. Sequence.sorted 这样的表达式进行访问或调用它们。
若是在同一嵌入层次中的两个嵌入类型拥有同名的字段或方法,那么涉及它们的选择表达式或调用表达式会由于编译器不能肯定被选择或调用的目标而形成一个编译错误。
匿名结构体类型
匿名结构体类型比命名结构体类型少了关键字type和类型名称,声明以下:
struct { Sortable sorted bool }
能够在数组类型、切片类型或字典类型的声明中,将一个匿名的结构体类型做为他们的元素的类型。还能够将匿名结构体类型做为一个变量的类型,例如:
var anonym struct { a int b string }
不过对于上面,更经常使用的作法就是在声明以匿名结构体类型为类型的变量的同时对其初始化,例如:
anonym := struct { a int b string }{0, "string"}
与命名结构体类型相比,匿名结构体类型更像是“一次性”的类型,它不具备通用性,经常被用在临时数据存储和传递的场景中。
在Go语言中,能够在结构体类型声明中的字段声明的后面添加一个字符串字面量标签,以做为对应字段的附加属性。例如:
type Person struct { Name string `json:"name"` Age uint8 `json:"age"` Address string `json:"addr"` }
如上的字段的字符串字面量标签通常有两个反引号包裹的任意字符串组成。而且,它应该被添加但在与其对应的字段的同一行的最右侧。
这种标签对于使用该结构体类型及其值的代码来讲是不可见的。可是,能够用标准库代码包 reflect 中提供的函数查看到结构体类型中字段的标签。这种标签经常会在一些特殊应用场景下使用,好比,标准库代码包 encoding/json 中的函数会根据这种标签的内容肯定与该结构体类型中的字段对应的 JSON 节点的名称。
结构体值通常由复合字面量(类型字面量和花括号构成)来表达。在Go语言中,经常将用于表示结构体值的复合字面量简称为结构体字面量。在同一个结构体字面量中,一个字段名称只能出现一次。例如:
Sequence{Sortable: SortableStrings{"3", "2", "1"}, sorted: false}
类型 SortableStrings 实现了接口类型 Sortable,这个能够在Go语言学习笔记4中了解到。这里就能够把一个 SortableStrings 类型的值赋给 Sortable 字段。
编写结构体字面量,还能够忽略字段的名称,但有以下的两个限制:
若是想要省略其中某个或某些键值对的键,那么其余的键值对的键也必须省略。
Sequence{ SortableStrings{"3", "2", "1"}, sorted: false} // 这是不合法的
Sequence{ sorted: false , Sortable: SortableStrings{"3", "2", "1"}} // 合法 Sequence{SortableStrings{"3", "2", "1"}, false} // 合法 Sequence{ Sortable: SortableStrings{"3", "2", "1"}} // 合法,未被明确赋值的字段的值会被其类型的零值填充。 Sequence{ false , SortableStrings{"3", "2", "1"}} // 不合法,顺序不一致,会编译错误 Sequence{ SortableStrings{"3", "2", "1"}} // 不合法,顺序不一致,会编译错误
在Go语言中,能够在结构体字面量中不指定任何字段的值。例如:
Sequence{} // 这种状况下,两个字段都被赋予它们所属类型的零值。
与数组类型相同,结构体类型属于值类型。结构体类型的零值就是如上的不为任何字段赋值的结构体字面量。
一个结构体类型的属性就是它所包含的字段和与它关联的方法。在访问权限容许的状况下,咱们可使用选择表达式访问结构体值中的字段,也可使用调用表达式调用结构体值关联的方法。
在Go语言中,只存在嵌入而不存在继承的概念。不能把前面声明的 List 类型的值赋给一个 Sequence 类型的变量,这样的赋值语句会形成一个编译错误。在一个结构体类型的别名类型的值上,既不能调用那个结构体类型的方法,也不能调用与那个结构体类型对应的指针类型的方法。别名类型不是它源类型的子类型,但别名类型内部的结构会与它的源类型一致。
对于一个结构体类型的别名类型来讲,它拥有源类型的所有字段,但这个别名类型并无继承与它的源类型关联的任何方法。
若是只是将 List 类型做为 Sequence 类型的一个别名类型,那么声明以下:
type List Sequence
此时,List 类型的值的表示方法与 Sequence 类型的值的表示方法同样,以下:
List{ SortableStrings{"4", "5", "6"}, false}
若是有一个 List 类型的值 List,那么选择表达式 list.sorted 访问的就是这个 List 类型的值的 sorted 字段,一样,咱们也能够经过选择表达式 list.Sortable 访问这个值的嵌入字段 Sortable。可是这个 List 类型目前却不包含与它的源类型 Sequence 关联的方法。
在Go语言中,虽然不少预约义类型都属于泛型类型(好比数组类型、切片类型、字典类型和通道类型),但却不支持自定义的泛型类型。为了使 Sequence 类型可以部分模拟泛型类型的行为特征,只是向它嵌入 Sortable 接口类型是不够的,须要对 Sortable 接口类型进行拓展。以下:
type GenericSeq interface { Sortable Append(e interface{}) bool Set(index int, e interface{}) bool Delete(index int) (interface{}, bool) ElemValue(index int) interface{} ElemType() reflect.Type value() interface{} }
如上的接口类型 GenericSeq 中声明了用于添加、修改、删除、查询元素,以及获取元素类型的方法。一个数据类型要实现 GenericSeq 接口类型,也必须实现 Sortable 接口类型。
如今,将嵌入到 Sequence 类型的 Sortable 接口类型改成 GenericSeq 接口类型,声明以下:
type Sequence struct { GenericSeq sorted bool elemType reflect.Type }
在如上的类型声明中,添加了一个 reflect.Type 类型(即标准库代码包 reflect 中的 Type 类型)的字段 elemType,目的用它来缓存 GenericSeq 字段中存储的值的元素类型。
为了可以在改变 GenericSeq 字段存储的值的过程当中及时对字段 sorted 和 elemType 的值进行修改,以下还建立了几个与 Sequence 类型关联的方法。声明以下:
func (self *Sequence) Sort() { self.GenericSeq.Sort() self.sorted = true } func (self *Sequence) Append(e interface{}) bool{ result := self. GenericSeq.Append(e) //省略部分代码 self.sorted = true //省略部分代码 return result } func (self *Sequence) Set(index int, e interface{}) bool { result := self. GenericSeq.Set(index, e) //省略部分代码 self.sorted = true //省略部分代码 return result } func (self *Sequence) ElemType() reflect.Type { //省略部分代码 self.elemType = self.GenericSeq.ElemType() //省略部分代码 return self.elemType }
如上的这些方法分别与接口类型 GenericSeq 或 Sortable 中声明的某个方法有着相同的方法名称和方法签名。经过这种方式隐藏了 GenericSeq 字段中存储的值的这些同名方法,并对它们进行了无缝扩展。
GenericSeq 接口类型的实现类型以及 Sequence 类型的完整实现代码 点击这里