Go语言结构体

Go语言结构体

什么是结构体?

结构体是用户定义的类型,表示字段的集合。它可以用在需要将数据分组为单个单元而不是将每个数据作为单独值的地方。

例如,员工有名字、姓氏和年龄。将这三个属性分组到一个名为Employee 的结构中是有意义的。

声明一个结构体

type Employee struct {  
    firstName string
    lastName  string
    age       int
}

上面的代码片段声明了一个带有 firstNamelastNameage 的结构类型。上面的Employee结构称为命名结构,因为它创建了一个名为Employee 的新数据类型,可以使用该数据类型来创建结构

通过在一行中声明属于同一类型的字段并后跟类型名称,还可以使该结构更加紧凑。在上面的结构体中firstNamelastName属于同一类型string,因此该结构体可以重写为

type Employee struct {  
    firstName, lastName string
    age                 int
}

虽然上面的语法节省了几行代码,但它并没有使字段声明变得明确。请避免使用上述语法。

创建命名结构

让我们使用以下简单程序声明一个Employee 的 struct 。

package main

import (  
    "fmt"
)

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

func main() {

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

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

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

Run in playground

在上面程序的第 7 行中,我们创建了一个Employee struct type 。

在上述程序的第 17 行中,emp1通过指定每个字段名称的值来定义结构体。字段的顺序不必与声明结构类型时字段名称的顺序相同。

在这种情况下。我们改变了位置lastName并将其移到最后。这将毫无问题地工作。

在上述程序的第 25 行中,emp2通过省略字段名称来定义。在这种情况下,有必要保持字段的顺序与结构声明中指定的顺序相同。

请避免使用此语法,因为它会导致很难确定哪个值对应哪个字段。

我们在这里指出这种格式只是为了了解这也是一种有效的语法

上面的程序打印出

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

创建匿名结构

可以在不创建新数据类型的情况下声明结构。这些类型的结构称为匿名结构

package main

import (  
    "fmt"
)

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

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

Run in playground

在上述程序的第 8 行中,定义了一个匿名结构变量。 emp3正如我们已经提到的,这个结构体被称为匿名结构体,因为它只创建一个新的结构体变量,并且没有像命名结构体那样定义任何新的结构体类型。

该程序输出,

Employee 3 {Andreah Nikola 31 5000}  

访问结构体的各个字段

.运算符用于访问结构体的各个字段。

package main

import (  
    "fmt"
)

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

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

Run in playground

上面程序中的emp6.firstName 是访问emp6结构体的firstName字段。在25 行号中。我们修改员工的工资。该程序打印,

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

结构体的零值

当定义一个结构体并且未使用任何值显式初始化它时,默认情况下该结构体的字段将被分配零值。

package main

import (  
    "fmt"
)

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

func main() {  
    var emp4 Employee //zero valued struct
    fmt.Println("First Name:", emp4.firstName)
    fmt.Println("Last Name:", emp4.lastName)
    fmt.Println("Age:", emp4.age)
    fmt.Println("Salary:", emp4.salary)
}

Run in playground

上面的程序定义了emp4但没有用任何值初始化。因此firstNamelastName被分配了string的零值(空字符串)""agesalary被分配了 int 的零值,即 0。

该程序打印,

First Name:  
Last Name:  
Age: 0  
Salary: 0  

也可以指定某些字段的值并忽略其余字段。在这种情况下,被忽略的字段被分配零值。

package main

import (  
    "fmt"
)

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

func main() {  
    emp5 := Employee{
        firstName: "John",
        lastName:  "Paul",
    }
    fmt.Println("First Name:", emp5.firstName)
    fmt.Println("Last Name:", emp5.lastName)
    fmt.Println("Age:", emp5.age)
    fmt.Println("Salary:", emp5.salary)
}

Run in playground

在上面的程序中,firstNamelastName已初始化,而agesalary未初始化。因此agesalary被赋予零值。该程序输出,

First Name: John  
Last Name: Paul  
Age: 0  
Salary: 0  

指向结构体的指针

也可以创建指向结构的指针。

package main

import (  
    "fmt"
)

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

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

Run in playground

上面程序中的emp8是指向Employee结构体的指针。访问结构体字段的语法:(*emp8).firstName(*emp8).age

该程序打印,

First Name: Sam  
Age: 55  

Go 语言为我们提供了使用emp8.firstName而不是显式取消引用(\*emp8).firstName来访问firstName字段的选项。

package main

import (  
    "fmt"
)

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

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

Run in playground

我们在上面的程序中使用emp8.firstName过访问该字段,并且该程序还输出

First Name: Sam  
Age: 55  

匿名字段

可以创建包含仅包含类型而不包含字段名称的字段的结构。此类字段称为匿名字段。

下面的代码片段创建了一个Person结构体,具有两个匿名字段的stringint

type Person struct {  
    string
    int
}

即使匿名字段没有显式名称,默认情况下匿名字段的名称就是其类型的名称。

例如,在上面的 Person 结构体中,虽然字段是匿名的,但默认情况下它们采用字段类型的名称。所以Personstruct 有 2 个字段,分别为 stringint

package main

import (  
    "fmt"
)

type Person struct {  
    string
    int
}

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

Run in playground

上述程序中, 我们使用其类型作为字段名称来访问 Person 结构体的匿名字段,分别为stringint。上述程序的输出是,

naveen  
50  

嵌套结构

结构体可能包含一个字段,而该字段又是一个结构体。这些类型的结构称为嵌套结构。

package main

import (  
    "fmt"
)

type Address struct {  
    city  string
    state string
}

type Person struct {  
    name    string
    age     int
    address Address
}

func main() {  
    p := Person{
        name: "Naveen",
        age:  50,
        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)
}

Run in playground

Person上面程序中的结构体有一个字段,而address该字段又是一个结构体。该程序打印

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

Promoted fields

属于结构中匿名结构字段的字段被称为Promoted fields,因为它们可以被访问,就好像它们属于包含匿名结构域的结构一样。

我可以理解这个定义非常复杂,所以让我们直接深入一些代码来理解这一点

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

在上面的代码片段中,Person结构体有一个匿名字段Address,它是一个结构体。

现在Address的字段被称为Promoted fields,因为它们可以像直接在结构本身中声明一样被访问结构体本身的字段citystatePerson

package main

import (  
    "fmt"
)

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

func main() {  
    p := Person{
        name: "Naveen",
        age:  50,
        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
}

Run in playground

在上面程序的第 29 和 30行,访问提升的字段citystate就好像它们在结构体中声明的一样,使用语法p.cityp.state。该程序打印,

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

导出的结构体和字段

如果结构类型以大写字母开头,那么它是导出类型并且可以从其他包访问。同样,如果结构体的字段以 大写字母开头,则可以从其他包访问它们。

让我们编写一个具有自定义包的程序来更好地理解这一点。

在目录中,创建一个learnpackage包含以下内容的文件。

package computer

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

}

上面的代码片段创建了一个包 computer,其中包含一个导出的结构类型Spec,其中包含两个导出字段MakerPrice一个未导出字段model

让我们从主包导入这个包并使用该Spec结构。

在目录中创建一个名为的main.go文件,并在其中写入以下程序


package main

import (  
    "structs/computer"
    "fmt"
)

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

structs文件夹应具有以下结构,

├── structs
│   ├── computer
│   │   └── spec.go
│   ├── go.mod
│   └── main.go

上面的程序,我们导入computer包。我们访问struct 的两个导出字段和Maker

运行程序:

Maker: apple  
Price: 50000  

如果我们尝试访问未导出的字段model,编译器会报错。我们把代码替换成下面的:

package main

import (  
    "structs/computer"
    "fmt"
)

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

在上面的程序的12中,我们尝试访问未导出的字段model。运行该程序将导致编译错误。

# structs
./main.go:12:13: unknown field 'model' in struct literal of type computer.Spec

由于model该字段未导出,因此无法从其他包访问它。

结构比较

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

package main

import (  
    "fmt"
)

type name struct {  
    firstName string
    lastName  string
}

func main() {  
    name1 := name{
        firstName: "Steve",
        lastName:  "Jobs",
    }
    name2 := name{
        firstName: "Steve",
        lastName:  "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{
        firstName: "Steve",
    }

    if name3 == name4 {
        fmt.Println("name3 and name4 are equal")
    } else {
        fmt.Println("name3 and name4 are not equal")
    }
    
}

Run in playground

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

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

Run in playground

在上面的程序中, image struct type 包含一个类型为map,不具有可比性,因此也无法进行比较。如果运行该程序,编译将失败并出现错误

./prog.go:20:12: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)

你可能感兴趣的:(Go语言教程,golang,开发语言,后端)