Go 系列教程 —— 第 16 部分:结构体

什么是结构体?

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

例如,一个职员有 firstNamelastName 和 age 三个属性,而把这些属性组合在一个结构体 employee 中就很合理。

结构体的声明

type Employee struct {
    firstName string
    lastName  string
    age       int
}

在上面的代码片段里,声明了一个结构体类型 Employee,它有 firstNamelastName 和age 三个字段。通过把相同类型的字段声明在同一行,结构体可以变得更加紧凑。在上面的结构体中,firstName 和 lastName 属于相同的 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: "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)
}

在线运行程序

在上述程序的第 7 行,我们创建了一个命名的结构体 Employee。而在第 15 行,通过指定每个字段名的值,我们定义了结构体变量 emp1。字段名的顺序不一定要与声明结构体类型时的顺序相同。在这里,我们改变了 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}

结构体的零值(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,却没有初始化任何值。因此 firstName 和 lastName 赋值为 string 的零值("" )。而 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)
}

在线运行程序

在上面程序中的第 14 行和第 15 行,我们初始化了 firstName 和 lastName age 和 salary 没有进行初始化。因此 age 和 salary 赋值为零值。该程序会输出:

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,接着给 firestName 和 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 是一个指向结构体 Employee 的指针。(*emp8).firstName 表示访问结构体 emp8 的 firstName 字段。该程序会输出:

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 结构体,它含有两个匿名字段 string 和 int

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 结构体有两个名为 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)
}

在线运行程序

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

提升字段(Promoted Fields)

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

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

在上面的代码片段中,Person 结构体有一个匿名字段 Address,而 Address 是一个结构体。现在结构体 Address 有 city 和 state 两个字段,访问这两个字段就像在 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.city 和 p.state,访问提升字段 city 和 state 就像它们是在结构体 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 有两个导出字段 Maker 和 Price,和一个未导出的字段 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 的两个导出字段 Maker 和 Price。执行命令 go install structs 和 workspacepath/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 类型的结构体变量。

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

作者:Nick Coghlan
译者:Noluye
校对:polaris1119

本文由 GCTT 原创编译,Go 中文网 荣誉推出

原文地址:https://www.cnblogs.com/marshhu/p/12264167.html

时间: 2024-10-20 20:05:54

Go 系列教程 —— 第 16 部分:结构体的相关文章

CRL快速开发框架系列教程十(导出对象结构)

本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框架系列教程四(删除数据) CRL快速开发框架系列教程五(使用缓存) CRL快速开发框架系列教程六(分布式缓存解决方案) CRL快速开发框架系列教程七(使用事务) CRL快速开发框架系列教程八(使用CRL.Package) CRL快速开发框架系列教程九(导入/导出数据) CRL快速开发框架系列教程十(

智能合约语言 Solidity 教程系列6 - 结构体与映射

Solidity 教程系列第6篇 - Solidity 结构体与映射.Solidity 系列完整的文章列表请查看分类-Solidity. 写在前面 Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊.智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 本系列文章一部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,另一部分是Solidity深入分析. 结构体(Structs) Solidity提供struct来定义自定义类型,自定义的类型是引用类型

Go 结构体的使用

结构体是用户定义的类型,表示若干个字段(Field)的集合.有时应该把数据整合在一起,而不是让这些数据没有联系.这种情况下可以使用结构体. 例如,一个职员有 firstName.lastName 和 age 三个属性,而把这些属性组合在一个结构体 employee 中就很合理. 结构体的声明基础 type Employee struct { firstName string lastName string age int } 在上面的代码片段里,声明了一个结构体类型 Employee,它有 fi

「C语言」「例题」结构体与共用体

本篇收集<C语言程序设计教程>第九章“结构体与共用体”的所有例题. 1 #include <stdio.h> 2 3 /*输入一个学生的基本信息, 4 包括学号.姓名.性别.年龄.出生日期.三门成绩, 5 输出该学生的基本信息和平均成绩*/ 6 7 struct date 8 { 9 int year;/* 年份 */ 10 int month;/* 月份 */ 11 int day; /* 日期 */ 12 }; 13 14 struct student 15 { 16 char

17-结构体

本文目录 一.什么是结构体 二.结构体的定义 三.结构体变量的定义 四.结构体的注意点 五.结构体的初始化 六.结构体的使用 七.结构体数组 八.结构体作为函数参数 九.指向结构体的指针 天来学习C语言中另外一种数据类型:结构体.结构体是经常用到的数据类型,使用频率不亚于指针,所以需要重视,不过用法非常简单. 一.什么是结构体 * 在实际应用中,我们通常需要由不同类型的数据来构成一个整体,比如学生这个整体可以由姓名.年龄.身高等数据构成,这些数据都具有不同的类型,姓名可以是字符串类型,年龄可以是

结构体,联合体,内存分配

#include<stdio.h> struct a{ char x[6]; int y; char z[6]; }; struct b{ int y; char x[6]; char z[6]; }; union c{ char x[6]; int y; char z[6]; }; int main() { printf("%d,%d,%d\n",sizeof(struct a),sizeof(struct b),sizeof(union c)); return 0; }

结构体变量的sizeof计算

结构体字节对齐准则: 1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除: 2. 结构体每个成员相对于结构体首地址的偏移量都是当前成员大小的整数倍,如有需要编译器会在成员之间加上填充字节: 3. 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节. 4. 在嵌套结构中需要把内部‘打散'后再进行对齐计算. // sizeof(结构体).cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #inc

C语言——结构体

C语言专门提供了一种构造类型来解决上述问题,这就是结构体,它允许内部的元素是不同类型的. 1.定义形式 结构体内部的元素,也就是组成成分,我们一般称为"成员". 结构体的一般定义形式为: 1 struct 结构体名{ 2 3 类型名1 成员名1; 4 5 类型名2 成员名2; 6 7 …… 8 9 类型名n 成员名n; 10 11 }; struct是关键字,是结构体类型的标志. 四.结构体的注意点 1.不允许对结构体本身递归定义 2.结构体内可以包含别的结构体 3.定义结构体类型,只

结构体struts的长度

在需要计算结构体大小的时候,涉及到的一个问题就是其对齐模数 计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus). 也就是说对齐模数就是这个数据类型占用的空间大小. 关于结构体长度的计算,我查了一下,有两种理解的方式: 方式一: 当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1(类型S强于T)的整数,我们就称类型S的对齐要求比