第六章 结构体
Go语言中结构体是带有成员的复合类型。结构体成员是由一系列成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:
- 字段拥自己的类型和值
- 字段名必须唯一
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型
6.1 定义结构体
结构体的定义格式如下:
type 类型名 struct {
字段1 类型
字段2 类型
...
}
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- struct{}:表示结构体类型,type 类型名 struct{}可以理解为将struct{}结构体定义为类型名的类型。
- 字段1、字段2......:表示结构体字段名。结构体中的字段名必须唯一。
示例如下:
type Point struct{
x int
y int
}
6.2 实例化结构体——为结构体分配内存并初始化
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。
6.2.1 基本的实例化形式
结构体本身是一种类型,以var的方式声明结构体即可完成实例化。
基本的实例化格式如下:
var ins T
- T为结构体类型。
- ins为结构体的实例。
示例如下:
type Point struct{
x int
y int
}
var p Point
p.x = 10
p.y = 20
使用“.”来访问结构体的成员变量,如p.x和p.y等。结构体成员变量的赋值方法与普通变量的一致。
6.2.2 创建指针类型的结构体
Go语言中,还可以使用new关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。
使用new的格式如下:
ins := new(T)
- T 为类型,可以是结构体、整型、字符串等
- ins: T 类型被实例化后保存到ins变量中,ins的类型为*T,属于指针。
Go 语言让我们可以像访问普通结构体一样使用“.”访问结构体指针的成员。
下面的例子定义了一个玩家(Player)的结构,玩家拥有名字、生命值和魔法值,实例化玩家(Player)结构体后,可对成员进行赋值。
type Player struct{
Name string
HealthPoint int
MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealTPoint = 300
经过new实例化的结构体实例在成员赋值上与基本实例化的写法一致。
6.2.3 取结构体的地址实例化
在Go语言中,对结构体进行“&”取地址操作时,视为对该类型进行一次new的实例化操作。取地址格式如下:
ins := &T{}
- T 表示结构体类型。
- ins 为结构体的实例,类型为*T,是指针类型。
下面使用结构体定义一个命令行指令,指令中包含名称、变量关联和注释等。对Command进行指针地址的实例化,并完成赋值过程。代码如下:
type Command struct{
Name string
Var *int
Comment string
}
// 对结构体进行赋值
var version int = 1
cmd := &Command()
cmd.Name = "version"
cmd.Var = &version
cmd,Comment = "show version"
取地址实例化是最广泛的一种结构体实例化方式。
6.3 初始化结构体的成员变量
初始化有两种方式:一种是字段“键值对”形式,另一种是多个值的列表形式。
6.3.1 使用“键值对”初始化结构体
结构体实例化后字段的默认值是字段类型的默认值,数值为0,字符串为空字符串,布尔为false,指针为nil等。
1. 键值对初始化的格式如下:
ins := 结构体类型名{
字段1: 值,
字段2: 值,
...
}
- 键值之间以“:”分隔;键值对之间以“,”分隔。
2. 使用键值对填充结构体的例子
type People struct {
name string
child *People
}
relation := &People{
name:"爷爷",
child: &People{
name: "爸爸",
child: &People{
name: "我",
},
},
}
6.3.2 使用多个值得列表初始化结构体
1. 多个值列表初始化结构体的书写格式
多个值使用逗号分隔初始化结构体,例如:
ins := 结构体类型名{
字段1的值,
字段2的值,
···
}
- 必须初始化结构体的所有字段。
- 每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 键值对与值列表的初始化形式不能混用。
2. 多个值列表初始化结构体的例子
package main
import "fmt"
type Address struct {
Province string
City string
ZipCode int
PhoneNumber string
}
func main() {
addr := Address {
"四川",
"成都",
610000,
"15678903479",
}
fmt.Println(addr)
}
输出结果:
{四川 成都 610000 15678903479}
6.3.3 初始化匿名结构体
匿名结构体没有类型名称,无须通过type关键字定义就可以直接使用。
1. 匿名结构体定义格式和初始化写法
匿名结构体的初始化写法由结构体定义和键值对初始化两部分组成。结构体定义时没有结构体类型名,只有字段和类型定义。
ins := struct{
字段1 字段类型
字段2 字段类型
...
}{
初始化字段1:值,
初始化字段2:值,
...
}
2. 使用匿名结构体的例子
package main
import "fmt"
// 打印消息类型,传入匿名结构体
func printMsgType(msg *struct{
id int
data string
}) {
fmt.Printf("%T\n", msg)
fmt.Println(msg)
}
func main() {
msg := &struct {
id int
data string
}{
1024,
"hello",
}
printMsgType(msg)
}
输出结果:
*struct { id int; data string }
&{1024 hello}
匿名结构体在使用时需要重新定义,造成大量的重复代码,因此开发中较少使用。
6.4 构造函数——结构体和类型的一系列初始化操作的函数封装
Go语言没有提供构造函数的功能。结构体的初始化过程可以使用函数封装实现。
6.4.1 模拟构造函数重载
使用结构体描述一只猫。
package main
import "fmt"
type Cat struct {
Color string
Name string
}
func NewCatByName(name string) *Cat {
return &Cat{
Name: name,
}
}
func NewCatByColor(color string) *Cat {
return &Cat{
Color: color,
}
}
func main() {
var cat1, cat2 *Cat
cat1 = NewCatByName("san")
cat2 = NewCatByColor("Black")
fmt.Println(cat1, cat2)
}
6.4.2 带有父子关系的结构体的构造和初始化——模拟父级构造调用
使用结构体描述猫和黑猫的关系时,将猫(Cat)的结构体嵌入黑猫(BlackCat)中,表示黑猫拥有猫的特性,然后再使用不同的构造函数分别构造出黑猫和猫两个结构体实例。
package main
import "fmt"
type Cat struct {
Color string
Name string
}
type BlackCat struct {
Cat //嵌入Cat,类似于派生
}
// "构造基类"
func NewCat(name, color string) *Cat {
return &Cat{
Name: name,
Color: color,
}
}
// "构造子类"
func NewBlackCat(name, color string) *BlackCat {
cat := &BlackCat{}
cat.Color = color
cat.Name = name
return cat
}
func main() {
cat := NewCat("san","write")
blackCat := NewBlackCat("san","black")
fmt.Println(cat, blackCat)
}