Go教程第十一篇:结构体

结构体

本文我们着重讲解一下Go语言中的结构体。

什么是结构体 ?

结构体是用户定义的数据类型,它表示了一组字段的集合。当我们需要把一组数据汇集成单一的逻辑单元而不是把这些数据各自处理成单独的类型。例如,雇员有姓氏、名称、年龄,那我们就可以把这三个字段构造成一个单独的结构体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结构体,我们称它为命名结构体,因为它创建了一个名为Employee的新类型。今后我们就可以使用Employee这个新类型创建一个Employee类型的结构体。

除此之外,在声明结构体时,还可以不声明一个新类型,这种类型的结构体被称为匿名结构体。

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。我们又定义了一个结构体变量emp1,给它的每个字段都赋予了值。像这样的情况,字段名称的顺序没必要一定要和结构体声明时的顺序保持一致。

同时,我们还定义了一个结构体变量emp2,在定义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)
}

在上面的程序中,我们定义了一个匿名结构体变量emp3。像我们之前提到的,这种结构体类型称为匿名结构体。因为它仅创建了一个结构体变量emp3,而并没有定义任何的新结构体类型。

程序的输出如下:

Employee 3 {Andreah Nikola 31 5000}

结构体的零值

当定义了一个结构体类型,并且没有显示地进行初始化时,结构体的所有字段都会被默认赋予零值。

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就会被赋予字符串的零值即:“”。而age和salary则会被赋予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)
}

程序的输出如下:

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

除此之外,还可以创建一个零值结构体,之后再给它的字段赋值。

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,之后又为它的firstName和lastName字段赋予值。程序的输出如下:

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是一个指向结构体变量的指针。(*emp8).firstName是用于访问emp8结构体的firstName字段。
程序的输出如下:

First Name: Sam
Age: 55

除了使用(*emp8).firstName访问firstName字段外,还可以使用emp8.firstName来访问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)
}

输出结果如下:

First Name: Sam
Age: 55

匿名字段

在创建结构体时,我们也可以只指定类型而不指定字段名。这些字段被称为匿名字段。
下面的代码片就创建了一个Person,该结构体有俩个匿名字段string和int。

package main

import (
    "fmt"
)

type Person struct {
    string
    int
}

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

程序的输出如下:

{Naveen 50}.

尽管匿名字段并没有名称,默认情况下,它的数据类型就是它的字段名称。例如Person结构体,虽然字段是匿名的,但是默认地它会拿字段的类型作为名称。因此,Person结构体拥有俩个字段,其名称分别为:string和int。

package main

import (
    "fmt"
)

type Person struct {
    string
    int
}

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

在上面的程序中,我们分别使用字段的数据类型作为它的字段名称来访问字段,程序的输出结果为:

{naveen 50}

嵌套结构体

结构体的字段除了可以是基本类型之外,还可以是结构体类型。这样的结构体,我们称为嵌套结构体。

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

结构体中的匿名的结构体字段被称为promoted字段,我们用一段代码来理解下。

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

在上面的代码片段中,Person结构体有一个匿名字段Address,Address同时也是一个结构体类型。现在Address结构体中的字段city和state被称为promoted字段,因为它们可以直接访问,就好像它们是直接在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
}

在上面的程序中,city和state是promoted字段,它们可以直接使用p.city和p.state访问,就像它们直接是在结构体p中声明一样。程序的输出如下:

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

导出结构体和字段

如果一个结构体类型以大写字母开头的话,那么它就是一个导出类型,导出类型可以在其他包中访问。相似地,如果结构体的字段以大写字母开头的话,他们可以在其他包中访问。

我们来写一个代码,理解这个。

在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包,它包含了一个结构体类型Spec,Spec里面有俩个导出类型Maker和Price和一个非导出字段model。我们在main包里面引入此包,并使用Spec结构体。

在structs目录下,创建一个main.go文件,并把下面的程序写入mian.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结构体的导出字段Maker以及Price。
在workspacepath/bin/structs下面执行go install structs命令。
程序将输出如下:

Spec: {apple  50000}

如果我们试图访问非导出字段model时,编译器会报错。

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

上面的程序中,报错如下:spec.model undefined (cannot refer to unexported field or method model。

结构的相等性

结构体是值类型并且其中的字段都是可比的话,在他们对应的字段都相等的情况下,这俩个结构体变量就是相等的。

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字段。因为string是可比较的数据类型,所以俩个name类型的结构体变量可以做比较。

在上面程序中,name1和name2是相等的,而name3和name4并不相等。程序将输出如下结果:

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类型的字段data。由于map是不可比的,所以image1和image2也是不可比的。如果你运行此程序的话,编译器会报错:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared).

源代码,请查看github 代码地址.

以上就是关于结构体的所有内容,感谢您的阅读。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 16. 结构体 什么是结构体? 结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一...
    泷汰泱阅读 134评论 0 0
  • 最近在复习golang,学习的东西,如果不使用,很快就会忘记。所以,准备复习完golang,做一个练手的小项目,加...
    若与阅读 956评论 0 9
  • 《Go语言四十二章经》第十八章 Struct 结构体 作者:李骁 18.1结构体(struct) Go 通过结构体...
    ffhelicopter阅读 2,007评论 0 3
  • 字符串 Go 语言中的字符串是一个字节切片。把内容放在双引号""之间,我们可以创建一个字符串。 Go 中的字符串是...
    kakarotto阅读 300评论 0 0
  • 峰终定律中讲到,人们往往对一件事情最深刻的时候,是在开头和结尾处,而在中间环节是没有太多的深刻记忆的。 那在做一件...
    慕颖渊阅读 150评论 0 1