golang之Struct什么是结构体struct?

最近在复习golang,学习的东西,如果不使用,很快就会忘记。所以,准备复习完golang,做一个练手的小项目,加深对golang的学习。今天开始公司,进入封闭式开发,所以每天晚上回来,学习golang时间比较少了。所以,争取一天一章的学习。

学习资源: https://github.com/Unknwon, 无闻,golang的大牛,他的文档网站很不错。教学也很好。golang的在线测试 https://play.golang.org,直接在浏览器中测试。

struct的主要内容:

结构体的定义和使用 使用字面量初始化 匿名结构和字段 结构体之间的比较和赋值 嵌入结构 提升字段 字段属性的访问使用 引用传递和值传递

什么是结构体struct?

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

例如,一个职员有 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: "youdi",
        age:       25,
        salary:    500,
        lastName:  "liang",
    }

    //creating structure without using field names
    emp2 := Employee{"hui", "liang", 26, 800}

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

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

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

该程序将输出:

Employee 1 {youdi liang 25 500}
Employee 2 {hui liang 26 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。上面我们已经提到,之所以称这种结构体是匿名的,是因为它只是创建一个新的结构体变量 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)
}

该程序定义了 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)
}

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

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

在上面程序中,我们访问了 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

提升字段(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
}

在上面代码中的第 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)
}

在上面程序,我们试图访问未导出的字段 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)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

编写高效简洁代码的那些招式1

高效的代码,每期都会给大家介绍一下编码的技巧,让我们代码更整洁更高效。我们会从python 语言切入,讲一讲如何写的代码更pythonic 。Pythonic ...

1866
来自专栏進无尽的文章

编码篇 - 正则表达式及其相关

有时我们需要在一大段长文本中过滤出我们需要的字段,或者检验该文本是否符合要求(该文本是否是邮箱,链接,电话号码或身份证),这时候就需要用到正则表达式了,当然我们...

1142
来自专栏跟着阿笨一起玩NET

VB.NET自我总结语法

1101
来自专栏跟着阿笨一起玩NET

VB.NET语法小结

2572
来自专栏Golang语言社区

浅谈Go语言中的结构体struct & 接口Interface & 反射

结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套;

2174
来自专栏Golang语言社区

Go语言struct类型详解

struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型person代表一个人的...

2977
来自专栏分布式系统和大数据处理

.Net中的反射(动态创建类型实例) - Part.4

在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它。可以说,前面三节,我们学习的都是反射是什么,在接下来的...

1413
来自专栏章鱼的慢慢技术路

Go语言相关练习_选择题(1)

 解析:Go语言的内存回收机制规定,只要有一个指针指向引用一个变量,那么这个变量就不会被释放(内存逃逸),因此在Go语言中返回函数参数或临时变量是安全的。

631
来自专栏Golang语言社区

Go语言struct类型详解

struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型person代表一个人的...

2854
来自专栏数据之美

Python Tips, Tricks, and Hacks

一、快速技巧 1.1、4 种引号 '  '''  "  """  print """I wish that I'd never heard him say...

2735

扫码关注云+社区

领取腾讯云代金券