结构体

什么是结构体

Golang中结构体用来将若干个整合在一起,是一自定义类型。当我们需要将多个数据字段整合成在一起整体使用时可以使用结构体。

例如,一个公司员工一般具有firstNamelastName以及age等属性。因此把这三个属性放到一个结构体employee内部来整体考虑比较好。

声明结构体

type Employee struct {
    firstName string
    lastName string
    age int
}

这里声明了一个结构体类型Employee,该结构体类型具有三个域分别是firstNamelastName以及age。每个域都有其对应的数据类型,如果两个域对应的数据类型相同,那么可以更加紧凑的声明在一行,比如这里的firstNamelastName都是string类型,因此可以声明如下:

type Employee struct {
    firstName, lastName string
    age int
}

由于这里新创建了一个数据类型Employee,因此我们称这里声明的结构体Employee具名结构体。注意这里的Employee是一种结构体数据类型。我们可以声明该类型的结构体变量:

var employee Employee

当然,我们也可以基于匿名结构体来声明结构体变量:

var employee struct {
    firstName, lastName string
    age int
}

这里我们使用匿名结构体来声明了结构体变量employee

创建结构体变量

首先我们来创建一个具名结构体变量。

package main

import "fmt"

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

func main() {
    emp1 := Employee{
        firstName: "Sam",
        age: 25,
        salary: 500,
        lastName: "Anderson",
    }

    emp2 := Employee{"Thomas", "Paul", 29, 800}

    fmt.Println("Employee1", emp1)
    fmt.Println("Employee2", emp2)
}

Go Playground在线运行 这里我们声明了一个具名结构体类型Employee,然后定义了该类型的结构体变量emp1emp1定义时显示的指明了结构体各个域名,采用这种方式定义结构体变量无需保持各个域的顺序,比如这里我们就将域lastName放到了最后。

接着我们定了另外一个结构体变量emp2,注意定义时隐掉了各个域的名称,因此需要保持各个域的顺序。

该程序执行结果如下:

Employee1 {Sam Anderson 25 500}
Employee2 {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("Employee3", emp3)
}

Go Playground在线运行 这里我们声明了一个匿名结构体变量emp3,这里我们并未声明一个新的结构体类型,而是通过如下匿名结构体类型创建了一个匿名结构体变量:

struct {
    firstName, lastName string
    age, salary int
}

该程序执行结果如下:

Employee3 {Andreah Nikola 31 5000}

结构体变量的零值

如果我们声明了一个结构体变量,但是并不进行初始化赋值,那么该结构体变量的每个域将初始化为对应类型的零值。

package main

import "fmt"

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

func main() {
    var emp4 Employee
    fmt.Println("Employee4", emp4)
}

Go Playground在线运行 这里声明了结构体变量emp4,但是并未进行初始化赋值。因此其各个域初始化为对应类型的零值。firstNamelastName初始化为string类型的零值""agesalary初始化为int类型的零值0。该程序输出为:

Employee4 {  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("Employee5", emp5)
}

Go Playground在线运行 这里我们声明结构体变量emp5时,仅初始化了域firstNamelastName,这时被忽略的域agesalary则初始化为int类型的零值0。该程序输出如下:

Employee5 {John Paul 0 0}

访问结构体变量的域

Golang中通过.运算符来访问结构体变量的某个域。

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

Go Playground在线运行 这里通过emp6.firstName来访问结构体emp6的域firstName。该程序输出如下:

First  Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000

我们也可以通过该方式来给结构体变量某个域赋值:

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("Employee7", emp7)
}

Go Playground在线运行 这里先声明了结构体变量emp7,但是并未进行人工初始化,因此各个域均为零值。接着我们设置了firstNamelastName的值。该程序输出如下:

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

Go Playground在线运行 这里的emp8是一个指向结构体变量的指针,指向的结构体变量类型为Employee。通过(*emp8).firstName可以访问该指针指向的结构体变量的属性firstName。该程序输出为:

First Name: Sam
Age: 55

前面(*emp8).firstName中首先对指针emp8进行了解引用*emp8,Golang允许我们简化成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)
}

Go Playground在线运行 该程序执行结果跟上一段程序保持一致。

匿名属性

我们创建的结构体类型中某些属性可以不指明属性名,仅指明其数据类型,结构体的这种属性称为匿名属性。下面我们创建一个结构体类型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)
}

Go Playground在线运行 这里结构体类型Person含有两个匿名属性,p := Person{"Naveen", 50}定义了一个该结构体类型的变量。该程序输出{Naveen 50}

尽快结构体的匿名属性不需要提供属性名,Golang中会默认使用其数据类型作为属性名。例如前面的结构体类型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)
}

Go Playground在线运行 这里我们通过匿名属性的数据类型作为属性名来访问对应的属性stringint。该程序输出如下:

{Naveen 50}

结构体嵌套

Golang中,结构体中的某个属性仍然可以是结构体,称这种结构体为嵌套结构体

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

Go Playground在线运行 这里结构体类型Person中的属性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的两个属性citystate可以通过父结构体Person直接访问,我们称Address的这两个属性称为提升属性

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
}

Go Playground在线运行 这里我们直接通过父结构体p来访问子结构体Address的属性citystate,看起来就好像这两个属性是父结构体p本身的属性一样。该程序输出为:

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

结构体类型导出、结构体类型属性导出

如果一个结构体类型名称以大写字母开头,那么该结构体类型是一种导出类型,导出类型可以被其他包访问。类似的,如果结构体类型的属性名称以大写字母开头,这个属性也可以被其他包访问。下面我们通过一个例子来理解下。

在Golang工作目录的src文件夹下面创建一个子文件夹structs用来存放我们的项目。然后在structs文件夹下再继续创建一个子文件夹computer。 在computer目录下创建文件spec.go,并输入如下代码:

package computer
type Spec struct { // 导出结构体类型供其他包访问
    Maker string // 导出属性供其他包访问
    model string // 非导出属性
    Price int // 导出属性供其他包访问
}

这里创建了一个包computer,导出了一个结构体类型Spec及其两个属性MakerPrice供其他包访问,另外一个属性model不是大写字母开头,因此未导出。下面我们在main包中引入包computer并使用。

在文件夹structs下面创建文件main.go,并输入如下代码:

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

前面的程序中我们引入了包computer,并且访问了其导出的结构体类型Spec及其导出的属性MakerPrice。我们可以通过如下命令来执行该程序:

go install structs
workspacepath/bin/structs

该程序输出如下:

Spec: {apple  50000}

如果我们尝试访问未导出的属性model,Golang编译器会报错:

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

这里我们访问了未导出的属性字段model。执行会发现编译器报错:

spec.model undefined (cannot refer to unexported field or method model)

判等

Golang中结构体是值类型(注意不是引用类型),如果两个结构体的所有属性类型兼容,那么这两个结构体就可以比较是否相等。当两个结构体的所有属性相等时这两个结构体也相等。

package main

import "fmt"

type name struct {
    firstName, 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")
    }
}

Go Playground在线运行 这里结构体变量name1name2的属性均为stringstring类型是可比较的,因此这两个结构体变量也是可比较的。输出如下:

name1 and name2 are equal
name3 and name4 are not equal

如果结构体变量的属性不可比较,比如map,那么该结构体也是不可比较的。

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

Go Playground在线运行 这里结构体类型image的属性datamap类型,而Golang中map类型本身是不可比较的(这点可以回顾下前面介map的部分),因此会报错:

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

Last updated