点击上方蓝色“Go语言中文网”关注,每天一起学 Go

欢迎来到 Golang 系列教程的第 16 个教程。

什么是结构体?

结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体。

例如,一个职员有 firstNamelastNameage 三个属性,而把这些属性组合在一个结构体 employee 中就很合理。

结构体的声明

type Employee struct {    firstName string    lastName  string    age       int}

在上面的代码片段里,声明了一个结构体类型 Employee,它有 firstNamelastNameage 三个字段。通过把相同类型的字段声明在同一行,结构体可以变得更加紧凑。在上面的结构体中,firstNamelastName 属于相同的 string 类型,于是这个结构体可以重写为:

type Employee struct {    firstName, lastName string    age, salary         int}

上面的结构体 Employee 称为 命名的结构体(Named Structure)。我们创建了名为 Employee 的新类型,而它可以用于创建 Employee 类型的结构体变量。

声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure)

var employee struct {    firstName, lastName string    age int}

上述代码片段创建一个匿名结构体 employee

创建命名的结构体

通过下面代码,我们定义了一个命名的结构体 Employee

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {

    //creating structure using field names    emp1 := Employee{        firstName: "Sam",        age:       25,        salary:    500,        lastName:  "Anderson",    }

    //creating structure without using field names    emp2 := Employee{"Thomas", "Paul", 29, 800}

    fmt.Println("Employee 1", emp1)    fmt.Println("Employee 2", emp2)}

在线运行程序[1]

在上述程序的第 7 行,我们创建了一个命名的结构体 Employee。而在第 15 行,通过指定每个字段名的值,我们定义了结构体变量 emp1。字段名的顺序不一定要与声明结构体类型时的顺序相同。在这里,我们改变了 lastName 的位置,将其移到了末尾。这样做也不会有任何的问题。

在上面程序的第 23 行,定义 emp2 时我们省略了字段名。在这种情况下,就需要保证字段名的顺序与声明结构体时的顺序相同。

该程序将输出:

Employee 1 {Sam Anderson 25 500}Employee 2 {Thomas Paul 29 800}

创建匿名结构体

package main

import (    "fmt")

func main() {    emp3 := struct {        firstName, lastName string        age, salary         int    }{        firstName: "Andreah",        lastName:  "Nikola",        age:       31,        salary:    5000,    }

    fmt.Println("Employee 3", emp3)}

在线运行程序[2]

在上述程序的第 3 行,我们定义了一个匿名结构体变量 emp3。上面我们已经提到,之所以称这种结构体是匿名的,是因为它只是创建一个新的结构体变量 em3,而没有定义任何结构体类型。

该程序会输出:

Employee 3 {Andreah Nikola 31 5000}

结构体的零值(Zero Value)

当定义好的结构体并没有被显式地初始化时,该结构体的字段将默认赋为零值。

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    var emp4 Employee //zero valued structure    fmt.Println("Employee 4", emp4)}

在线运行程序[3]

该程序定义了 emp4,却没有初始化任何值。因此 firstNamelastName 赋值为 string 的零值("")。而 agesalary 赋值为 int 的零值(0)。该程序会输出:

Employee 4 { 0 0}

当然还可以为某些字段指定初始值,而忽略其他字段。这样,忽略的字段名会赋值为零值。

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    emp5 := Employee{        firstName: "John",        lastName:  "Paul",    }    fmt.Println("Employee 5", emp5)}

在线运行程序[4]

在上面程序中的第 14 行和第 15 行,我们初始化了 firstNamelastName,而 agesalary 没有进行初始化。因此 agesalary 赋值为零值。该程序会输出:

Employee 5 {John Paul 0 0}

访问结构体的字段

点号操作符 . 用于访问结构体的字段。

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    emp6 := Employee{"Sam", "Anderson", 55, 6000}    fmt.Println("First Name:", emp6.firstName)    fmt.Println("Last Name:", emp6.lastName)    fmt.Println("Age:", emp6.age)    fmt.Printf("Salary: $%d", emp6.salary)}

在线运行程序[5]

上面程序中的 emp6.firstName 访问了结构体 emp6 的字段 firstName。该程序输出:

First Name: SamLast Name: AndersonAge: 55Salary: $6000

还可以创建零值的 struct,以后再给各个字段赋值。

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    var emp7 Employee    emp7.firstName = "Jack"    emp7.lastName = "Adams"    fmt.Println("Employee 7:", emp7)}

在线运行程序[6]

在上面程序中,我们定义了 emp7,接着给 firstNamelastName 赋值。该程序会输出:

Employee 7: {Jack Adams 0 0}

结构体的指针

还可以创建指向结构体的指针。

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    emp8 := &Employee{"Sam", "Anderson", 55, 6000}    fmt.Println("First Name:", (*emp8).firstName)    fmt.Println("Age:", (*emp8).age)}

在线运行程序[7]

在上面程序中,emp8 是一个指向结构体 Employee 的指针。(*emp8).firstName 表示访问结构体 emp8firstName 字段。该程序会输出:

First Name: SamAge: 55

Go 语言允许我们在访问 firstName 字段时,可以使用 emp8.firstName 来代替显式的解引用 (*emp8).firstName

package main

import (    "fmt")

type Employee struct {    firstName, lastName string    age, salary         int}

func main() {    emp8 := &Employee{"Sam", "Anderson", 55, 6000}    fmt.Println("First Name:", emp8.firstName)    fmt.Println("Age:", emp8.age)}

在线运行程序[8]

在上面的程序中,我们使用 emp8.firstName 来访问 firstName 字段,该程序会输出:

First Name: SamAge: 55

匿名字段

当我们创建结构体时,字段可以只有类型,而没有字段名。这样的字段称为匿名字段(Anonymous Field)。

以下代码创建一个 Person 结构体,它含有两个匿名字段 stringint

type Person struct {    string    int}

我们接下来使用匿名字段来编写一个程序。

package main

import (    "fmt")

type Person struct {    string    int}

func main() {    p := Person{"Naveen", 50}    fmt.Println(p)}

在线运行程序[9]

在上面的程序中,结构体 Person 有两个匿名字段。p := Person{"Naveen", 50} 定义了一个 Person 类型的变量。该程序输出 {Naveen 50}

虽然匿名字段没有名称,但其实匿名字段的名称就默认为它的类型。比如在上面的 Person 结构体里,虽说字段是匿名的,但 Go 默认这些字段名是它们各自的类型。所以 Person 结构体有两个名为 stringint 的字段。

package main

import (    "fmt")

type Person struct {    string    int}

func main() {    var p1 Person    p1.string = "naveen"    p1.int = 50    fmt.Println(p1)}

在线运行程序[10]

在上面程序的第 14 行和第 15 行,我们访问了 Person 结构体的匿名字段,我们把字段类型作为字段名,分别为 "string" 和 "int"。上面程序的输出如下:

{naveen 50}

嵌套结构体(Nested Structs)

结构体的字段有可能也是一个结构体。这样的结构体称为嵌套结构体。

package main

import (    "fmt")

type Address struct {    city, state string}type Person struct {    name string    age int    address Address}

func main() {    var p Person    p.name = "Naveen"    p.age = 50    p.address = Address {        city: "Chicago",        state: "Illinois",    }    fmt.Println("Name:", p.name)    fmt.Println("Age:",p.age)    fmt.Println("City:",p.address.city)    fmt.Println("State:",p.address.state)}

在线运行程序[11]

上面的结构体 Person 有一个字段 address,而 address 也是结构体。该程序输出:

Name: NaveenAge: 50City: ChicagoState: Illinois

提升字段(Promoted Fields)

如果是结构体中有匿名的结构体类型字段,则该匿名结构体里的字段就称为提升字段。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。我知道这种定义很复杂,所以我们直接研究下代码来理解吧。

type Address struct {    city, state string}type Person struct {    name string    age  int    Address}

在上面的代码片段中,Person 结构体有一个匿名字段 Address,而 Address 是一个结构体。现在结构体 Addresscitystate 两个字段,访问这两个字段就像在 Person 里直接声明的一样,因此我们称之为提升字段。

package main

import (    "fmt")

type Address struct {    city, state string}type Person struct {    name string    age  int    Address}

func main() {    var p Person    p.name = "Naveen"    p.age = 50    p.Address = Address{        city:  "Chicago",        state: "Illinois",    }    fmt.Println("Name:", p.name)    fmt.Println("Age:", p.age)    fmt.Println("City:", p.city) //city is promoted field    fmt.Println("State:", p.state) //state is promoted field}

在线运行程序[12]

在上面代码中的第 26 行和第 27 行,我们使用了语法 p.cityp.state,访问提升字段 citystate 就像它们是在结构体 p 中声明的一样。该程序会输出:

Name: NaveenAge: 50City: ChicagoState: Illinois

导出结构体和字段

如果结构体名称以大写字母开头,则它是其他包可以访问的导出类型(Exported Type)。同样,如果结构体里的字段首字母大写,它也能被其他包访问到。

让我们使用自定义包,编写一个程序来更好地去理解它。

在你的 Go 工作区的 src 目录中,创建一个名为 structs 的文件夹。另外在 structs 中再创建一个目录 computer

computer 目录中,在名为 spec.go 的文件中保存下面的程序。

package computer

type Spec struct { //exported struct    Maker string //exported field    model string //unexported field    Price int //exported field}

上面的代码片段中,创建了一个 computer 包,里面有一个导出结构体类型 SpecSpec 有两个导出字段 MakerPrice,和一个未导出的字段 model。接下来我们会在 main 包中导入这个包,并使用 Spec 结构体。

package main

import "structs/computer"import "fmt"

func main() {    var spec computer.Spec    spec.Maker = "apple"    spec.Price = 50000    fmt.Println("Spec:", spec)}

包结构如下所示:

src   structs        computer            spec.go        main.go

在上述程序的第 3 行,我们导入了 computer 包。在第 8 行和第 9 行,我们访问了结构体 Spec 的两个导出字段 MakerPrice。执行命令 go install structsworkspacepath/bin/structs,运行该程序。

如果我们试图访问未导出的字段 model,编译器会报错。将 main.go 的内容替换为下面的代码。

package main

import "structs/computer"import "fmt"

func main() {    var spec computer.Spec    spec.Maker = "apple"    spec.Price = 50000    spec.model = "Mac Mini"    fmt.Println("Spec:", spec)}

在上面程序的第 10 行,我们试图访问未导出的字段 model。如果运行这个程序,编译器会产生错误:spec.model undefined (cannot refer to unexported field or method model)

结构体相等性(Structs Equality)

结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。如果两个结构体变量的对应字段相等,则这两个变量也是相等的

package main

import (    "fmt")

type name struct {    firstName string    lastName string}

func main() {    name1 := name{"Steve", "Jobs"}    name2 := name{"Steve", "Jobs"}    if name1 == name2 {        fmt.Println("name1 and name2 are equal")    } else {        fmt.Println("name1 and name2 are not equal")    }

    name3 := name{firstName:"Steve", lastName:"Jobs"}    name4 := name{}    name4.firstName = "Steve"    if name3 == name4 {        fmt.Println("name3 and name4 are equal")    } else {        fmt.Println("name3 and name4 are not equal")    }}

在线运行程序[13]

在上面的代码中,结构体类型 name 包含两个 string 类型。由于字符串是可比较的,因此可以比较两个 name 类型的结构体变量。

上面代码中 name1name2 相等,而 name3name4 不相等。该程序会输出:

name1 and name2 are equalname3 and name4 are not equal

如果结构体包含不可比较的字段,则结构体变量也不可比较。

package main

import (    "fmt")

type image struct {    data map[int]int}

func main() {    image1 := image{data: map[int]int{        0: 155,    }}    image2 := image{data: map[int]int{        0: 155,    }}    if image1 == image2 {        fmt.Println("image1 and image2 are equal")    }}

在线运行程序[14]

在上面代码中,结构体类型 image 包含一个 map 类型的字段。由于 map 类型是不可比较的,因此 image1image2 也不可比较。如果运行该程序,编译器会报错:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)

github[15] 上有本教程的源代码。

上一教程 - 指针

下一教程 - 方法[16]



推荐阅读

  • Go 经典入门系列 15:指针

福利我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook获取;还可以回复「进群」,和数万 Gopher 交流学习。

结构体怎么赋值_Go 经典入门系列 16:结构体相关推荐

  1. golang 切片 接口_Go 经典入门系列 18:接口(一)

    点击上方蓝色"Go语言中文网"关注,每天一起学 Go 欢迎来到 Golang 系列教程[1]的第 18 个教程.接口共有两个教程,这是我们接口的第一个教程. 什么是接口? 在面向对 ...

  2. go io.reader 多次读取_Go 经典入门系列 24:Select

    点击上方蓝色"Go语言中文网"关注,每天一起学 Go 欢迎来到 Golang 系列教程[1]的第 24 篇. 什么是 select? select 语句用于在多个发送/接收信道操作 ...

  3. SAP PM 入门系列16 - KO88对维护工单做结算

    SAP PM 入门系列16 - KO88对维护工单做结算 1, 如下的维护工单100316384, 该工单settlement rule信息, Settle type, FUL & PER , ...

  4. 02.Web大前端时代之:HTML5+CSS3入门系列~H5结构元素

    Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 1.结构元素 可以理解为语义话标记,比如:以前这么写&l ...

  5. c语言定义指向结构体的指针,C语言入门教程-指向结构体的指针

    指向结构体的指针 在C语言中几乎可以创建指向任何类型的指针,包括用户自定义的类型.创建结构体指针是极常见的.下面是一个例子: typedef struct {char name[21];char ci ...

  6. 深度学习入门系列1:多层感知器概述

    本人正在学习<deep learning with python>–Jason Brownlee,有兴趣的可以一起学习. 仅供学习参考,不做商用! 大家好,我技术人Howzit,这是深度学 ...

  7. C语言入门系列一之C语言介绍、环境配置和运行执行

    鉴于书本和教材文字过多.又难懂的缺点,所以我打算写一系列通俗移动的C语言入门教程,让大家在短时间内学习和掌握C语言,以及遇到C语言的一些问题该如何得到快速的解决.希望这些系列的教程,能够帮助到大家!该 ...

  8. 深度学习入门系列23:项目:用爱丽丝梦游仙境生成文本

    大家好,我技术人Howzit,这是深度学习入门系列第二十三篇,欢迎大家一起交流! 深度学习入门系列1:多层感知器概述 深度学习入门系列2:用TensorFlow构建你的第一个神经网络 深度学习入门系列 ...

  9. 传智播客linux安装视频教程下载,传智播客 LAMP(Linux)经典入门教程 李东超主讲29集完整版教程下载...

    课程名称 传智播客 LAMP(Linux)经典入门教程 李东超主讲29集完整版教程下载 课程目录 李东超 PHP视频教程 LAMP经典入门 第01讲 Linux的基本概念和发展历史.wmv 李东超 P ...

最新文章

  1. android 拼图课程设计,Flash拼图游戏制作课程设计报告
  2. Use PowerShell to Replace netdom Commands to Join the Domain
  3. 关于Python正则表达式的问题
  4. 【bzoj3524】【Poi2014】【Couriers】可持久化线段树(主席树)水题
  5. IIS6下PHP的ISAPI和FastCGI性能比较 期待ii7
  6. 腾讯视频如何下载视频_腾讯视频如何做明星装扮
  7. Python学习心路历程
  8. 天邑ty400 wifi6路由 安装第三方扩展 Entware
  9. 老打印机USB改无线连接
  10. python 图像处理基本操作
  11. Proxy(代理)服务器
  12. html 中数字换行,CSS实现连续数字和英文的自动换行的方法
  13. 2018年秋招运维岗面试常见python和数据结构知识点总结
  14. 线段树辅助——扫描线法计算矩形面积并
  15. 搭建springboot+mybatis+freemarker项目
  16. Metasploit(一) 利用 MS17-010 漏洞进行攻击
  17. 安装kalibr踩坑5:E: Package ‘python-software-properties‘ has no installation candidate
  18. 【OpenGL基础】|| OpenGL渲染过程介绍
  19. cds大佬座谈会总结
  20. CREO 用最简单的方法做个莫比乌斯带

热门文章

  1. BILIBILI 高并发实时弹幕系统的实战之路 | 架构师实践日
  2. 第七天总结:字符编码
  3. Netty源码分析第6章(解码器)----第4节: 分隔符解码器
  4. oh-my-robot
  5. jquery 使用animate来改变高度自动添加样式overflow:hidden的问题
  6. win8.1配置java环境个人 win8.1
  7. Java虚拟机JVM学习05 类加载器的父委托机制
  8. abaqus中元素过度失真是什么意思_Abaqus三维多晶体脚本建模Voronoi多面体建模
  9. python xmxl 无法启动_/usr/bin/python: can't decompress data; zlib not available 的异常处理
  10. OpenGL中投影变换矩阵的反向推导