go语言学习-结构体(十六)

翻译自:https://golangbot.com/structs/

什么是结构体?

结构体是用户定义的类型,表示若干个字段的集合。有时候应该将多个数据分组到一个整体中,而不是将每个数据作为单独的类型进行维护。这种情况下可以使用结构体。

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

结构体的声明
type Employee struct {  
    firstName string
    lastName  string
    age       int
}

上面的代码片段声明了一个名为 Employee 的结构体类型,它拥有 firstName, lastNameage 三个字段。属于同一类型的多个字段可以写到一行,后面是类型名称。在上面的结构体中 firstNamelastName 都是 string 类型,因此可以将它们写在一起。

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

上面的结构体 Employee 称为 命名的结构体Named Structure)。因为创建了名为 Employee 的新类型,而它可以用于创建 Employee 类型的结构体变量。

我们也可以定义一个没有类型名称的结构体,这种结构体叫做匿名结构体Anonymous Structures)。

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

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

该程序输出:

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 但是没有初始化。因此 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)
}

在上面的程序第1415行,firstNamelastName 被初始化,而 agesalary 没有。因此 agesalary 被指定为int零值。程序的输出为:

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 先被定义,然后给 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 中我们可以使用 emp8.firstName 来访问 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 类型,尽管它的字段是匿名的,但是默认它们拥有它们的类型的名字。因此 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)
}

在上面的程序第1415行,我们通过使用匿名字段的类型名("string" 和 "int")来访问 Person 结构体的字段。程序的输出为:

{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),因为可以从外部结构体变量直接访问匿名结构体类型中的字段,就像这些字段原本属于外部结构体一样。我知道这个定义可能很难理解,所以让我们直接进入一些代码来理解这个:

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

上面的程序片段中,Person 结构体有一个匿名字段 Address,这个匿名字段也是一个结构体。现在 Address 中的字段 citystate 被称为提升字段,因为它们就好像被直接声明在 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
}

上面的程序第 2627 行,提升字段 citystate 就像声明在结构体变量 p 中一样,通过 p.cityp.state 的方式被访问。程序的输出如下:

Name: Naveen  
Age: 50  
City: Chicago  
State: Illinois  
导出结构体和字段

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

让我们编写一个包含自定义包的程序,以便更好地理解这一点。

在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,其中包含一个导出类型的结构体 Spec,以及它的两个字段 MakerPrice,它还有一个未导出字段 model。让我们从main包导入这个包并使用 Spec 结构体。

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

在上面程序的第3行中,我们导入了computer包。在第8行和第9行,我们访问了结构体Spec的两个导出字段MakerPrice。可以通过执行命令 go install structsworkspacepath/bin/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)
}

在上述程序的第10行中,我们尝试访问未导出的字段model。运行此程序将导致编译错误:

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 类型的变量也是可以比较的。

在上面的程序中,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 结构体类型包含了一个字段 data,其类型是 mapmap 是不可比较的类型,因此 image1image2是不可比较的。如果你运行这个程序,将报错:

main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值