目录
Go语言结构体
Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
Go 语言中的类型可以被实例化,使用new
或&
构造的类型实例的类型是类型的指针。
结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:
- 字段拥有自己的类型和值。
- 字段名必须唯一。
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型。
Go语言的类
- Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。
- Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。
- Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。
Go语言结构体定义
Go语言可以通过自定义的方式形成新的类型,结构体就是这些类型中的一种复合类型,结构体是由零个或多个任意类型的值聚合成的实体,每个值都可以称为结构体的成员。
结构体成员也可以称为“字段”,这些字段有以下特性:
- 字段拥有自己的类型和值;
- 字段名必须唯一;
- 字段的类型也可以是结构体,甚至是字段所在结构体的类型。
使用关键字 type
可以将各种基本类型定义为自定义类型,基本类型包括整型、字符串、布尔等。结构体是一种复合的基本类型,通过 type 定义为自定义类型后,使结构体更便于使用。
结构体的定义格式如下:
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型
…
}
对各个部分的说明:
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- struct{}:表示结构体类型,type 类型名 struct{}可以理解为将 struct{} 结构体定义为类型名的类型。
- 字段1、字段2……:表示结构体字段名,结构体中的字段名必须唯一。
- 字段1类型、字段2类型……:表示结构体各个字段的类型。
使用结构体可以表示一个包含 X 和 Y 整型分量的点结构,代码如下:
type Point struct {
X int
Y int
}
同类型的变量也可以写在一行,颜色的红、绿、蓝 3 个分量可以使用 byte 类型表示,定义的颜色结构体如下:
type Color struct {
R, G, B byte
}
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。
Go语言实例化结构体——为结构体分配内存并初始化
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存,因此必须在定义结构体并实例化后才能使用结构体的字段。
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的内存是完全独立的。Go语言可以通过多种方式实例化结构体,根据实际需要可以选用不同的写法。
基本的实例化形式
结构体本身是一种类型,可以像整型、字符串等类型一样,以 var 的方式声明结构体即可完成实例化。基本实例化格式如下:
// T 为结构体类型,ins 为结构体的实例
var ins T
用结构体表示的点结构(Point)的实例化过程请参见下面的代码:
type Point struct {
X int
Y int
}
var p Point
p.X = 10
p.Y = 20
在例子中,使用.
来访问结构体的成员变量,如p.X
和p.Y
等,结构体成员变量的赋值方法与普通变量一致。
创建指针类型的结构体
Go语言中,还可以使用 new 关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。使用 new 的格式如下:
// T 为类型,可以是结构体、整型、字符串等
// ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针
ins := new(T)
Go语言让我们可以像访问普通结构体一样使用.
来访问结构体指针的成员。
下面的例子定义了一个玩家(Player)的结构,玩家拥有名字、生命值和魔法值,实例化玩家(Player)结构体后,可对成员进行赋值,代码如下:
type Player struct{
Name string
HealthPoint int
MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300
经过 new 实例化的结构体实例在成员赋值上与基本实例化的写法一致。Go语言和 C/C++中实例化的区别是什么?在 C/C++ 语言中,使用 new 实例化类型后,访问其成员变量时必须使用->
操作符。在Go语言中,访问结构体指针的成员变量时可以继续使用.
,这是因为Go语言为了方便开发者访问结构体指针的成员变量,使用了语法糖(Syntactic sugar)技术,将 ins.Name 形式转换为 (*ins).Name。
取结构体的地址实例化
在Go语言中,对结构体进行&
取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式如下:
// T 表示结构体类型
// ins 为结构体的实例,类型为 *T,是指针类型
ins := &T{}
下面使用结构体定义一个命令行指令(Command),指令中包含名称、变量关联和注释等,对 Command 进行指针地址的实例化,并完成赋值过程,代码如下:
// 定义 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"
取地址实例化是最广泛的一种结构体实例化方式,可以使用函数封装上面的初始化过程,代码如下:
func newCommand(name string, varref *int, comment string) *Command {
return &Command{
Name: name,
Var: varref,
Comment: comment,
}
}
cmd = newCommand(
"version",
&version,
"show version",
)
Go语言初始化结构体的成员变量
结构体在实例化时可以直接对成员变量进行初始化,初始化有两种形式分别是以字段“键值对”形式和多个值的列表形式,键值对形式的初始化适合选择性填充字段较多的结构体,多个值的列表形式适合填充字段较少的结构体。
使用“键值对”初始化结构体
结构体可以使用“键值对”(Key value pair)初始化字段,每个“键”(Key)对应结构体中的一个字段,键的“值”(Value)对应字段需要初始化的值。键值对的填充是可选的,不需要初始化的字段可以不填入初始化列表中。结构体实例化后字段的默认值是字段类型的默认值,例如 ,数值为 0、字符串为 “”(空字符串)、布尔为 false、指针为 nil 等。
键值对初始化结构体的书写格式如下:
// 键值对初始化的格式
// 结构体类型:定义结构体时的类型名称
ins := 结构体类型名{
// 字段1、字段2:结构体成员的字段名
// 结构体类型名的字段初始化列表中,字段名只能出现一次
// 字段1的值、字段2的值:结构体成员字段的初始值
字段1: 字段1的值,
字段2: 字段2的值,
…
}
键值之间以:
分隔,键值对之间以,
分隔。
使用键值对填充结构体的例子。下面示例中描述了家里的人物关联,正如儿歌里唱的:“爸爸的爸爸是爷爷”,人物之间可以使用多级的 child 来描述和建立关联,使用键值对形式填充结构体的代码如下:
// 定义 People 结构体
type People struct {
// 结构体的字符串字段
name string
// 结构体的结构体指针字段,类型是 *People
child *People
}
// relation 由 People 类型取地址后,形成类型为 *People 的实例
relation := &People{
name: "爷爷",
// child 在初始化时,需要 *People 类型的值,使用取地址初始化一个 People
child: &People{
name: "爸爸",
child: &People{
name: "我",
},
},
}
注意:结构体成员中只能包含结构体的指针类型,包含非指针类型会引起编译错误
使用多个值的列表初始化结构体
Go语言可以在“键值对”初始化的基础上忽略“键”,也就是说,可以使用多个值的列表初始化结构体的字段。
// 多个值列表初始化结构体的书写格式
// 多个值使用逗号分隔初始化结构体
ins := 结构体类型名{
字段1的值,
字段2的值,
…
}
使用这种格式初始化时,需要注意:
- 必须初始化结构体的所有字段。
- 每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 键值对与值列表的初始化形式不能混用。
多个值列表初始化结构体的例子。下面的例子描述了一段地址结构,地址要求具有一定的顺序,例如:
type Address struct {
Province string
City string
ZipCode int
PhoneNumber string
}
addr := Address{
"四川",
"成都",
610000,
"0",
}
fmt.Println(addr)
运行代码,输出如下:
{四川 成都 610000 0}
初始化匿名结构体
匿名结构体没有类型名称,无须通过 type 关键字定义就可以直接使用。
匿名结构体定义格式和初始化写法。匿名结构体的初始化写法由结构体定义和键值对初始化两部分组成,结构体定义时没有结构体类型名,只有字段和类型定义,键值对初始化部分由可选的多个键值对组成,如下格式所示:
ins := struct {
// 匿名结构体字段定义
// 字段1、字段2……:结构体定义的字段名
// 字段类型1、字段类型2……:结构体定义字段的类型
字段1 字段类型1
字段2 字段类型2
…
}{
// 字段值初始化
// 初始化字段1、初始化字段2……:结构体初始化时的字段名,可选择性地对字段初始化
// 字段1的值、字段2的值……:结构体初始化字段的初始值
初始化字段1: 字段1的值,
初始化字段2: 字段2的值,
…
}
键值对初始化部分是可选的,不初始化成员时,匿名结构体的格式变为:
ns := struct {
字段1 字段类型1
字段2 字段类型2
…
}
使用匿名结构体的例子。在本示例中,使用匿名结构体的方式定义和初始化一个消息结构,这个消息结构具有消息标示部分(ID)和数据部分(data),打印消息内容的 printMsg() 函数在接收匿名结构体时需要在参数上重新定义匿名结构体,代码如下:
package main
import (
"fmt"
)
// 打印消息类型, 传入匿名结构体
// 定义 printMsgType() 函数,参数为 msg,类型为*struct{id int data string}
// 因为类型没有使用 type 定义,所以需要在每次用到的地方进行定义
func printMsgType(msg *struct {
id int
data string
}) {
// 使用动词%T打印msg的类型
fmt.Printf("%T\n", msg)
}
func main() {
// 实例化一个匿名结构体同时初始化成员
msg := &struct { // 定义部分
// 定义匿名结构体的字段
id int
data string
}{ // 值初始化部分
// 给匿名结构体字段赋予初始值
1024,
"hello",
}
// 将 msg 传入 printMsgType() 函数中进行函数调用
printMsgType(msg)
}
代码输出如下:
*struct { id int; data string }
匿名结构体的类型名是结构体包含字段成员的详细描述,匿名结构体在使用时需要重新定义,造成大量重复的代码,因此开发中较少使用。