结构体
Q7nl1s admin

什么是结构体?

结构体是表示字段集合的用户定义类型。它可以用于将数据分组为单个单元而不是将其中每个单元作为单独值有意义的值。

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

声明结构

1
2
3
4
5
type Employee struct {  
firstName string
lastName string
age int
}

上面的片段声明了一个结构类型Employee,其字段为firstNamelastNameage。上述Employee结构被称为命名结构,因为它创建了一个名为Employee的新数据类型,可以用它来创建Employee结构。

通过在单行中声明属于同一类型的字段,并在后面加上类型名称,也可以使这个结构更加紧凑。在上面的结构中,firstNamelastName属于同一类型的字符串,因此该结构可以改写为

1
2
3
4
type Employee struct {  
firstName, lastName string
age int
}

尽管上述语法节省了几行代码,但它并没有使字段的声明变得明确。请不要使用上述语法。

创建命名结构

让我们使用以下简单程序声明一个命名结构Employee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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)
}

在上述程序的第7行,我们创建了一个名为Employee的结构类型。在上述程序的第17行,通过指定每个字段名的值来定义emp1结构。字段的顺序不一定要和声明结构类型时字段名的顺序相同。在这种情况下,我们改变了lastName的位置,把它移到了最后。这样做没有任何问题。

在上述程序的第25行,通过省略字段名定义了emp2。在这种情况下,有必要保持字段的顺序与结构声明中指定的相同。请不要使用这种语法,因为它使我们很难弄清哪个值是哪个字段的。我们在这里指定这种格式只是为了了解这也是一种有效的语法 :)

以上程序打印:

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

创建匿名结构

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
}

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

该程序输出:

1
Employee 3 {Andreah Nikola 31 5000}  

访问结构的各个字段

.操作符用于访问结构的各个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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)
}

在上面的程序中,emp6.firstName访问emp6结构中的firstName字段。在第25行,我们修改了雇员的工资。这个程序打印:

1
2
3
4
5
First Name: Sam  
Last Name: Anderson
Age: 55
Salary: $6000
New Salary: $6500

结构的零值

当定义了一个结构,并且没有明确地用任何值对其进行初始化时,该结构的字段会被默认为零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)
}

上面的程序定义了emp4,但是它没有被初始化任何值。因此,firstNamelastName被分配为字符串的零值,即空字符串"",年龄,工资被分配为int的零值,即0。 这个程序打印出来。

1
2
3
4
First Name:  
Last Name:
Age: 0
Salary: 0

也可以为某些字段指定数值而忽略其他字段。在这种情况下,被忽略的字段被赋予零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

在上述程序的第16和17行中,firstNamelastName被初始化,而agesalary没有被初始化。因此,年龄和工资被分配为零值。这个程序的输出:

1
2
3
4
First Name: John  
Last Name: Paul
Age: 0
Salary: 0

指向结构的指针

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

在上面的程序中,emp8是一个指向Employee结构的指针。(*emp8).firstName是访问emp8结构的firstName字段的语法。这个程序的打印结果。

1
2
First Name: Sam  
Age: 55

Go语言让我们可以选择使用emp8.firstName而不是显式的通过解引用(*emp8).firstName来访问firstName字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
}

我们在上面的程序中使用了emp8.firstName来访问firstName字段,这个程序也有输出。

1
2
First Name: Sam  
Age: 55

匿名字段

我们可以创建带有字段的结构,这些字段只包含一个类型而没有字段名。这类字段被称为匿名字段。

下面的片段创建了一个Person结构,其中有两个匿名字段stringint

1
2
3
4
type Person struct {  
string
int
}

即使匿名字段没有明确的名称,默认情况下,匿名字段的名称是其类型的名称。例如,在上面的Person结构中,虽然字段是匿名的,但默认情况下它们采用字段类型的名称。因此,Person 结构有 2 个字段,名称为stringint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)
}

在上述程序的第17行和第18行中。在上述程序的第17行和第18行中,我们使用字段名的类型访问Person结构的匿名字段,即stringint。上述程序的输出结果是。

1
2
naveen  
50

嵌套结构

一个结构有可能包含一个字段,而这个字段又是一个结构。这类结构被称为嵌套结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)
}

上述程序中的Person结构有一个 address 字段,而这个字段又是一个结构。该程序会打印出:

1
2
3
4
Name: Naveen  
Age: 50
City: Chicago
State: Illinois

晋升字段

属于结构中匿名结构字段的字段被称为晋升字段,因为它们可以被访问,就像它们属于持有匿名结构字段的结构一样。我可以理解,这个定义相当复杂,所以让我们直接通过一些代码来理解这个问题:)。

1
2
3
4
5
6
7
8
9
type Address struct {  
city string
state string
}
type Person struct {
name string
age int
Address
}

在上面的代码片段中,Person结构有一个匿名字段Address,这是一个结构。现在,Address的字段(即citystate)被称为晋升字段,因为它们可以被访问,就像它们直接在Person结构本身中声明一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
}

在上述程序的第29行和第30行中,使用语法p.cityp.state来访问晋升字段citystate,就像它们在结构p本身中声明一样。这个程序会打印出来。

1
2
3
4
Name: Naveen  
Age: 50
City: Chicago
State: Illinois

导出的结构和字段

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

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

在你的E:\backend\Go_backend目录下创建一个名为structs的文件夹。请随意在您喜欢的地方创建。我更喜欢我的工作目录目录。

1
mkdir E:\backend\Go_backend\structs 

让我们创建一个名为 structs 的 go 模块。

1
2
cd E:\backend\Go_backend\structs
go mod init structs

在结构体中创建另一个目录 computer

1
mkdir computer  

computer 目录中,创建一个文件 spec.go,内容如下:

1
2
3
4
5
6
7
8
package computer

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

}

上述片段创建了一个包 computer,其中包含一个导出的结构类型 Spec,有两个导出的字段 MakerPrice 以及一个未导出的字段 model。让我们从主包中导入这个包并使用Spec结构。

structs目录下创建一个名为main.go的文件,在main.go中编写以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
}

struct文件夹应具有以下结构。

1
2
3
4
5
├── structs
│ ├── computer
│ │ └── spec.go
│ ├── go.mod
│ └── main.go

在上述程序的第4行中,我们导入了computer包。在第13行和第14行中,我们访问了Spec结构中的两个导出字段MakerPrice。通过执行go installstructs`命令,可以运行本程序。如果您不清楚如何运行Go程序,请访问https://golangbot.com/hello-world-gomod/#1goinstall,了解更多信息。

1
2
go install  
structs

运行上述命令将打印:

1
2
Maker: apple  
Price: 50000

如果我们试图访问未导出的字段模型,编译器会报错。用以下代码替换main.go的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,运行这个程序将导致编译错误。

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

因为model字段是未导出的,它不能从其他包中访问。

结构的平等性

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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")
}
}

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

在上述程序中,name1name2是相等的,而name3name4则不是。这个程序将输出:

1
2
name1 and name2 are equal  
name3 and name4 are not equal

如果结构变量包含不具有可比性的字段,那么它们就不具有可比性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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字段,该字段是map类型的。maps是不能比较的,因此image1image2不能被比较。如果你运行这个程序,编译将失败,并出现错误:

1
./prog.go:20:12: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View