结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。学过 C 或 C++ 的人都应该熟悉结构体,但在 Go 中,没有像 C++ 中的 class
类的概念,只有 struct
结构体的概念,所以也没有继承。
结构体的声明
在 Go 中使用下面的语法是对结构体的声明。
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
例如下面是定义一个存储个人资料名为 Person
的结构体。
type Person struct {
name string
age int
gender string
}
上面的代码声明了一个结构体类型 Person
,它有 name
、 age
和 gender
三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。
type Person struct {
name, gender string
age int
}
上面的结构体 Person
称为 命名的结构体(Named Structure) 。我们创建了名为 Person
的新类型,而它可以用于创建 Person
类型的结构体变量。
声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure) 。
var person struct {
name, gender string
age int
}
上面的代码创建了一个匿名结构体 person
。
创建命名的结构体
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
// 使用字段名创建结构体
per1 := Person{
name: "John",
gender: "male",
age: 18,
}
// 不使用字段名创建结构体
per2 := Person{"Mary", "female", 20}
fmt.Println("person 1 ", per1)
fmt.Println("person 2 ", per2)
}
上面的例子使用了两种方法创建了结构体,第一种是在创建结构体时使用字段名对每个字段进行初始化,而第二种方法是在创建结构体时不使用字段名,直接按字段声明的顺序对字段进行初始化。
运行该程序会输出:
person 1 {John male 18}
person 2 {Mary female 20}
创建匿名结构体
package main
import "fmt"
func main() {
// 创建匿名结构体变量
per3 := struct {
name, gender string
age int
} {
name: "John",
gender: "male",
age: 20,
}
fmt.Println("person 3 ", per3)
}
上面的程序定义了一个匿名结构体变量 per3
。运行该程序会输出:
person 3 {John male 20}
结构体的零值(Zero Value)
当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
// 不初始化结构体
var per4 = Person{}
fmt.Println("person 4 ", per4)
}
上面的程序定义了 per4
,但没有对其进行初始化,所以, name
和 gender
字段赋值为 string
类型的零值 ""
,而 age
字段赋值为 int
类型的零值 0
。运行该程序会输出:
person 4 { 0}
为结构体指定字段赋初值
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
// 为结构体指定字段赋初值
var per5 = Person{
name: "John",
gender: "male",
}
fmt.Println("person 5 ", per5)
}
上面的结构体变量 per5
只初始化了 name
和 gender
字段, age
字段没有初始化,所以会被初始化为零值。运行该程序输出如下:
person 5 {John male 0}
访问结构体的字段
点操作符 .
用于访问结构体的字段。
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
var per6 = Person{
name: "John",
gender: "male",
age: 30,
}
fmt.Println("person 6 name: ", per6.name)
fmt.Println("person 6 gender: ", per6.gender)
fmt.Println("person 6 age: ", per6.age)
}
上面的程序访问了结构体变量 per6
的每个字段,运行该程序输出如下:
person 6 name: John
person 6 gender: male
person 6 age: 30
当然,使用点操作符 .
可以用于对结构体的字段的赋值。
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
var per7 = Person{}
per7.name = "John"
per7.gender = "male"
per7.age = 30
fmt.Println("person 7 ", per7)
}
运行该程序输出如下:
person 7 {John male 30}
指向结构体的指针
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
per8 := &Person{"John", "male", 30}
fmt.Println("person 8 name: ", (*per8).name)
fmt.Println("person 8 name: ", per8.name)
}
在上面的程序中, per8
是一个指向结构体 Person
的指针,上面用 (*per8).name
访问 per8
的 name
字段,上面的 per8.name
代替显式的解引用 (*per8).name
访问 per8
的 name
字段。
person 8 name: John
person 8 name: John
匿名字段
在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field) 。
package main
import "fmt"
type Person struct {
string
int
}
func main() {
per9 := Person{"John", 30}
fmt.Println("person 9 ", per9)
fmt.Println("person 9 string: ", per9.string)
fmt.Println("person 9 int: ", per9.int)
}
上面的程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但其实匿名字段的名称默认就是它的类型。所以上面的结构体 Person
有两个名为 string
和 int
的字段。运行上面的程序输出如下:
person 9 {John 30}
person 9 string: John
person 9 int: 30
嵌套结构体
结构体的字段也可能是一个结构体,这样的结构体称为 嵌套结构体(Nested Structs) 。
package main
import "fmt"
type Date struct {
year int
month int
day int
}
type Person struct {
name string
age int
birthday Date
}
func main() {
per10 := Person{
name: "John",
age: 11,
}
per10.birthday = Date{
year: 2010,
month: 1,
day: 20,
}
fmt.Println("person 10 name:", per10.name)
fmt.Println("person 10 age:", per10.age)
fmt.Println("person 10 birthday year:", per10.birthday.year)
fmt.Println("person 10 birthday month:", per10.birthday.month)
fmt.Println("person 10 birthday day:", per10.birthday.day)
}
上面的程序 Person
结构体有一个字段 birthday
,而且它的类型也是一个结构体 Date
,运行该程序输出如下:
person 10 name: John
person 10 age: 11
person 10 birthday year: 2010
person 10 birthday month: 1
person 10 birthday day: 20
提升字段
结构体中如果有匿名的结构体类型字段,则该匿名结构体里的字段就称为 提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。就像刚刚上面的程序,如果我们把 Person
结构体中的字段 birthday
直接用匿名字段 Date
代替, Date
结构体的字段例如 year
就不用像上面那样使用 per10.birthday.year
访问,而是使用 per10.year
就能访问 Date
结构体中的 year
字段。现在结构体 Date
有 year
、 month
和 day
三个字段,访问这三个字段就像在 Person
里直接声明的一样,因此我们称之为提升字段。
package main
import "fmt"
type Date struct {
year int
month int
day int
}
type Person struct {
name string
age int
Date
}
func main() {
per10 := Person{
name: "John",
age: 11,
}
per10.Date = Date{
year: 2010,
month: 1,
day: 20,
}
fmt.Println("person 10 name:", per10.name)
fmt.Println("person 10 age:", per10.age)
fmt.Println("person 10 birthday year:", per10.year)
fmt.Println("person 10 birthday month:", per10.month)
fmt.Println("person 10 birthday day:", per10.day)
}
运行上面的程序输出如下:
person 10 name: John
person 10 age: 11
person 10 birthday year: 2010
person 10 birthday month: 1
person 10 birthday day: 20
结构体比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 ==
或 !=
运算符进行比较。相等比较运算符 ==
将比较两个结构体的每个成员,因此下面两个比较的表达式是等价的:
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
per11 := Person{
name: "John",
age: 11,
}
per12 := Person{
name: "John",
age: 11,
}
fmt.Println(per11.name == per12.name && per11.age == per12.age) // true
fmt.Println(per11 == per12) // true
}
给结构体定义方法
在 Go 中无法在结构体内部定义方法,当我们可以使用组合函数的方式来定义结构体方法。
package main
import "fmt"
// Person 定义一个名为 Person 的结构体
type Person struct {
name string
age int
}
// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (person Person) PrintPersonInfo() {
fmt.Println("name:", person.name)
fmt.Println("age:", person.age)
}
func main() {
per13 := Person{
name: "John",
age: 30,
}
per13.PrintPersonInfo()
}
上面的程序中定义了一个与结构体 Person
绑定的方法 PrintPersonInfo()
,其中 PrintPersonInfo
是方法名, (person Person)
表示将此方法与 Person
的实例绑定,这里我们把 Person
称为方法的接收者,而 person
表示实例本身,相当于 Python 中的 self
,在方法内可以使用 person.attribute_name
来访问实例属性。运行该程序输出如下:
name: John
age: 30
方法的参数传递方式
如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。
package main
import "fmt"
// Person 定义一个名为 Person 的结构体
type Person struct {
name string
age int
}
// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (person Person) PrintPersonInfo() {
fmt.Println("name:", person.name)
fmt.Println("age:", person.age)
}
// AddPersonAge 定义一个与 Person 的绑定的方法,使 age 值加 n
func (person *Person) AddPersonAge(n int) {
person.age = person.age + n
}
func main() {
per13 := Person{
name: "John",
age: 30,
}
fmt.Println("before add age")
per13.PrintPersonInfo()
per13.AddPersonAge(5)
fmt.Println("after add age")
per13.PrintPersonInfo()
}
运行该程序输出如下:
before add age
name: John
age: 30
after add age
name: John
age: 35
内部方法与外部方法
在 Go 语言中,函数名通过首字母大小写实现控制对方法的访问权限。
参考文献:
[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.
👇周一至周五更新,期待你的关注👇
联系客服