结构体
结构体:是一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体.
结构体的定义格式:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
...
}
类型名:标识自定义结构体的名称,在同一个包内不能重复。
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型。
// AnimalCategory 代表动物分类学中的基本分类法。
type AnimalCategory struct {
kingdom string // 界。
phylum string // 门。
class string // 纲。
order string // 目。
family string // 科。
genus string // 属。
species string // 种。
}
结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以向声明内置类型一样使用 var 关键字声明结构体类型。
var 结构体实例 结构体类型
var animal AnimalCategory
我们通过.来访问结构体的字段
结构体初始化
没有初始化的结构体,其成员变量都是对应类型的零值。
(1)使用键值对初始化,这种方式当某些字段没有初始值时可以不写,没有初始值的字段就是该类型的零值
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
a:1,b:2,c:3,
}
fmt.Println(n) //{1 2 3 0}
(2)使用值的列表初始化,这种初始化方式必须要初始化结构体的所有字段,且初始值的填充顺序必须与字段在结构体中的声明顺序一致(内部会转换成键值对的方式)
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1,2,3,4,
}
fmt.Println(n) //{1 2 3 4}
匿名结构体:在函数体中直接定义的结构体,格式是:var 变量 struct{字段名 字段类型 …},之后初始化使用。
结构体占用的是一块连续的内存
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
输出结果如下:
n.a 0xc00000e8c0
n.b 0xc00000e8c1
n.c 0xc00000e8c2
n.d 0xc00000e8c3
注意:空结构体是不占用空间的
构造函数
在go语言中是没有构造函数的,可以通过代码实现类似结构函数的功能
type person struct {
name string
city string
age int8
}
func newPerson(name, city string, age int8) *person {
return &person{
name:name,
city:city,
age:age,
}
}
func main(){
p1 := newPerson("test","beijing",int8(18))
fmt.println("type:%T vaule:%#v\n", p1,p1)
}
方法
为了方便理解,我们把方法和函数放在一起进行比较。
函数定义:
func 函数名(参数列表)(返回值){
函数体
}
方法的定义:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数){
函数体
}
接收者变量:名字可以按照变量命名规则起名,一般遵循驼峰法,不使用_
接收者类型:只能是自定义数据类型,且不能是接口类型(这个后续再讲)。
接收者类型有两种:
值方法的接收者是该方法所属的那个类型值的一个副本,该方法内对该副本的修改一般不会体现在原值上,除非这个类型本身就是某个引用类型(比如切片)的别名类型。
指针方法的接收者是该方法所属的那个基本类型值的指针值的一个副本,该方法内对该副本指向的值进行修改,一定会体现在原值上。
函数和方法的区别:
函数是独立的实体,申明函数的时候可以有函数名字,也可以没有函数名字(匿名函数),可以当做普通的值传来传去.
方法不同,方法需要名字,不能被当做值来看待,而且必须隶属于某一个类型,方法所属的类型会通过其声明中的接收者声明体现出来。
package main
import "fmt"
// AnimalCategory 代表动物分类学中的基本分类法。
type AnimalCategory struct {
kingdom string // 界。
phylum string // 门。
class string // 纲。
order string // 目。
family string // 科。
genus string // 属。
species string // 种。
}
// 方法 String() 是结构体类型 AnimalCategory 的方法
func (ac AnimalCategory) String() string {
return fmt.Sprintf("%s%s%s%s%s%s%s",ac.kingdom, ac.phylum,
ac.class, ac.order, ac.family, ac.genus, ac.species)
}
func main(){
var ac1 AnimalCategory
ac1.species = "cat"
fmt.Println(ac1)
fmt.Println(ac1.String())
}
方法String()是结构体类型AnimalCategory的一个方法,因此当我们将结构体AnimalCategory实例化之后,ac1就可以直接调用String()方法。
匿名字段
结构体的匿名字段:结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。但是匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
Go语言规范规定,如果一个字段的声明中只有字段的类型名而没有字段的名称,那么它就是一个嵌入字段,也可以被称为匿名字段。
我们可以通过此类型变量的名称后跟“.”,再后跟嵌入字段类型的方式引用到该字段。也就是说,嵌入字段的类型既是类型也是名称。
// AnimalCategory 代表动物分类学中的基本分类法。
type AnimalCategory struct {
kingdom string // 界。
phylum string // 门。
class string // 纲。
order string // 目。
family string // 科。
genus string // 属。
species string // 种。
}
func (ac AnimalCategory) String() string {
return fmt.Sprintf("%s%s%s%s%s%s%s",
ac.kingdom, ac.phylum, ac.class, ac.order,
ac.family, ac.genus, ac.species)
}
type Animal struct {
scientificName string // 学名。
AnimalCategory // 动物基本分类。
}
func (a Animal) Category() string {
return a.AnimalCategory.String()
}
Animal结构体中嵌入了结构体AnimalCategory作为嵌入字段,所以animal可以使用AnimalCategory的字段和方法。
一个结构体中可以嵌套包含另一个结构体或结构体指针。把一个结构体类型嵌入到另一个结构体类型中的意义不止如此。嵌入字段的方法集合会被无条件地合并进被嵌入类型的方法集合中。
一个数据类型关联的所有方法,共同组成了该类型的方法集合。同一个方法集合中的方法不能出现重名,并且如果它们所属的是同一个结构体,那么他们的名称与该类型中的任何字段的名字也不能重复。
那么问题来了:
1.如果Animal也有一个方法也是String(),会调用哪个呢?
答案是,animal的String方法会被调用。这时,我们说,嵌入字段AnimalCategory的String方法被“屏蔽”了。注意,只要名称相同,无论这两个方法的签名是否一致,被嵌入类型的方法都会“屏蔽”掉嵌入字段的同名方法。
2.如果处于同一个层级的多个嵌入字段拥有同名的字段或方法,那么从被嵌入类型的值那里,选择此名称的时候就会引发一个编译错误,因为编译器无法确定被选择的成员到底是哪一个。
3.多层嵌入:“屏蔽”现象会以嵌入的层级为依据,嵌入层级越深的字段或方法越可能被“屏蔽”。
结构体嵌套很像继承,Go语言中根本没有继承的概念,它所做的是通过嵌入字段的方式实现了类型之间的组合.




