打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Go 语言系列14:结构体

结构体(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 ,它有 nameagegender 三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。

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 ,但没有对其进行初始化,所以, namegender 字段赋值为 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 只初始化了 namegender 字段, 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 访问 per8name 字段,上面的 per8.name 代替显式的解引用 (*per8).name 访问 per8name 字段。

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 有两个名为 stringint 的字段。运行上面的程序输出如下:

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 字段。现在结构体 Dateyearmonthday 三个字段,访问这三个字段就像在 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 语言中,函数名通过首字母大小写实现控制对方法的访问权限。

  • 当方法的首字母为 大写 时,这个方法对于 所有包 都是 Public ,其他包可以随意调用。
  • 当方法的首字母为 小写 时,这个方法是 Private ,其他包是无法访问的。

参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.

👇周一至周五更新,期待你的关注👇

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
go语言
Go语言基础之结构体
Go语言方法与接口保姆级教程!
Go语言struct类型详解
Golang的ORM框架之gorm
Go语言基础之结构体(秋日篇)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服