结构体

结构体

上一节:第十五篇 指针
下一节:第十七篇 方法

这是本Golang系列教程的第16篇。

什么是结构体?

结构体(struct)是用户自定义的类型,它代表若干字段的集合。有些时候将多个数据看做一个整体要比单独使用这些数据更有意义,这种情况下就适合使用结构体。

比如将一个员工的 firstName, lastName 和 age 三个属性打包在一起成为一个 employee 结构就是很有意义的。

结构体的声明

type Employee struct {  
    firstName string
    lastName  string
    age       int
}

上面的代码片段声明了一个名为 Employee 的结构体类型,它拥有 firstName,lastName 和 age 三个字段。同一类型的多个字段可以合并到一行(用逗号分隔),并将类型放在后面。上面的结构体中 firstName 与 lastName 都是 string 类型,因此可以将它们写在一起。

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

上面的结构体 Employee 是一个具名结构体(named structure),因为它创建了一个具有名字的结构体类型: Employee 。我们可以定义具名结构体类型的变量。

我们也可以定义一个没有类型名称的结构体,这种结构体叫做匿名结构体(anonymous structures)

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)
}

在上面的程序中,我们定义了一个名为 Employee 的结构体类型。我们可以通过指定字段的名称和对应的值来创建一个结构体变量,比如在第15行,我们就是通过这种方式定义了 Employee 类型的一个结构体变量 empl。这里字段名称的顺序没必要和声明结构体类型时的一致。例如这里我们将 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)
}

在上面的程序中,第3行定义了一个 匿名结构体变量 emp3。正如我们提到的一样,这种结构体成为匿名结构体,因为它只创建了一个新的结构体变量 emp3,而没有定义新的结构体类型。

程序的输出为:

Employee 3 {Andreah Nikola 31 5000}  

结构体变量的 0 值

当定义一个结构体变量,但是没有给它提供初始值,则对应的字段被赋予它们各自类型的0值。

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)
}

上面的程序定义了 emp4 但是没有赋予任何初值。因此 firstName 和 lastName 被赋值为 string 类型的0值,也就是空字符串。age 和 salary 被赋值为 int 类型的0值,也就是 0 。程序的输出为:

Employee 4 {  0 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)
}

在上面的程序中,第14和15行,firstName 和 lastName 被提供了初始值,而 age 和 salary 没有。因此 age 和 salary 被指定为0值。程序的输出为:

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)
}

在上面的程序中,通过 emp6.firstName 访问 emp6 中的字段 firstName。程序的输出为:

First Name: Sam  
Last Name: Anderson  
Age: 55  
Salary: $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)
}

在上面程序中,我们定义了 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)
}

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

First Name: Sam
Age: 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)
}

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

First Name: Sam
Age: 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)
}

在上面的程序中,结构体 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)
}

在上面程序的第 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)
}

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

Name: Naveen  
Age: 50  
City: Chicago  
State: Illinois

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

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
}

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

Name: Naveen  
Age: 50  
City: Chicago  
State: 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")
    }
}

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

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

name1 and name2 are equal  
name3 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")
    }
}

在上面代码中,结构体类型image 包含一个 map 类型的字段。由于 map 类型是不可比较的,因此 image1image2 也不可比较。如果运行该程序,编译器会报错:

main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)

结构体的介绍到此结束。感谢阅读。

若文章对你有帮助,可以点赞或打赏支持我们。发布者:Aurora,转载请注明出处:http://61.174.243.28:13541/AY-knowledg-hub/16-%e7%bb%93%e6%9e%84%e4%bd%93/

(0)
AuroraAurora站点维系者
上一篇 2023年 12月 5日 下午5:55
下一篇 2023年 12月 5日 下午5:57

相关推荐

  • Helm | Helm 展示readme

    文章目录helm show readme简介可选项从父命令继承的命令请参阅 helm show readme 显示chart的README 简介 该命令检查chart(目录、文件或…

    入门教程 2023年 12月 14日
  • more

    文章目录more补充说明语法选项参数实例 more 显示文件内容,每次显示一屏 补充说明 more命令 是一个基于vi编辑器文本过滤器,它以全屏幕的方式按页显示文本文件的内容,支持…

    入门教程 2024年 1月 3日
  • exec

    文章目录exec补充说明语法选项参数实例 exec 调用并执行指定的命令 补充说明 exec命令 用于调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的…

    入门教程 2023年 12月 14日
  • tailf

    文章目录tailf补充说明语法选项参数实例 tailf 在屏幕上显示指定文件的末尾若干行内容,通常用于日志文件的跟踪输出 补充说明 tailf命令几乎等同于tail -f,严格说来…

    入门教程 2024年 3月 11日
  • 29. Defer

    欢迎来到 Golang 系列教程的第 29 篇。 文章目录什么是 defer?示例延迟方法实参取值(Arguments Evaluation)defer 栈defer 的实际应用 …

    2023年 12月 5日
  • iOS环境搭建

    文章目录iOS Xcode 安装界面生成器(Interface Builder)iOS模拟器 iOS Xcode 安装 1、从 https://developer.apple.co…

    2023年 3月 18日
  • modetest

    文章目录modetest补充说明安装语法选项参数实例 modetest DRM/KMS驱动程序libdrm中的模式测试工具 补充说明 modetest 是一个用于测试和验证 DRM…

    入门教程 2024年 1月 3日
  • sysctl

    文章目录sysctl补充说明语法选项参数实例配置sysctl sysctl 时动态地修改内核的运行参数 补充说明 sysctl命令 被用于在内核运行时动态地修改内核的运行参数,可用…

    入门教程 2024年 3月 11日
  • Mutex

    文章目录MutexMutex含有竞态条件的程序使用 Mutex使用信道处理竞态条件Mutex vs 信道 Mutex 上一节:第二十四篇 Select下一节:第二十六篇 结构体取代…

    2023年 12月 5日
  • Java 接口

    接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。 接…

    入门教程 2023年 3月 9日

发表回复

登录后才能评论
Translate »