什么是结构体? 结构体是表示字段集合的用户定义类型。它可以用于将数据分组为单个单元而不是将其中每个单元作为单独值有意义的值。
例如,员工有名字、姓氏和年龄。将这三个属性分组到名为Employee
的单个结构中是有意义的。
声明结构 1 2 3 4 5 type Employee struct { firstName string lastName string age int }
上面的片段声明了一个结构类型Employee
,其字段为firstName
、lastName
和age
。上述Employee
结构被称为命名结构,因为它创建了一个名为Employee
的新数据类型,可以用它来创建Employee结构。
通过在单行中声明属于同一类型的字段,并在后面加上类型名称,也可以使这个结构更加紧凑。在上面的结构中,firstName
和lastName
属于同一类型的字符串,因此该结构可以改写为
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 mainimport ( "fmt" ) type Employee struct { firstName string lastName string age int salary int } func main () { emp1 := Employee{ firstName: "Sam" , age: 25 , salary: 500 , lastName: "Anderson" , } 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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" ) type Employee struct { firstName string lastName string age int salary int } func main () { var emp4 Employee fmt.Println("First Name:" , emp4.firstName) fmt.Println("Last Name:" , emp4.lastName) fmt.Println("Age:" , emp4.age) fmt.Println("Salary:" , emp4.salary) }
上面的程序定义了emp4
,但是它没有被初始化任何值。因此,firstName
和lastName
被分配为字符串的零值,即空字符串""
,年龄,工资被分配为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 mainimport ( "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行中,firstName
和lastName
被初始化,而age
和salary
没有被初始化。因此,年龄和工资被分配为零值。这个程序的输出:
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 mainimport ( "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 mainimport ( "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结构,其中有两个匿名字段string
和int
1 2 3 4 type Person struct { string int }
即使匿名字段没有明确的名称,默认情况下,匿名字段的名称是其类型的名称。例如,在上面的Person结构中,虽然字段是匿名的,但默认情况下它们采用字段类型的名称。因此,Person 结构有 2 个字段,名称为string
和 int
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "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结构的匿名字段,即string
和int
。上述程序的输出结果是。
嵌套结构 一个结构有可能包含一个字段,而这个字段又是一个结构。这类结构被称为嵌套结构。
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 mainimport ( "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
的字段(即city
和state
)被称为晋升字段,因为它们可以被访问,就像它们直接在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 mainimport ( "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) fmt.Println("State:" , p.state) }
在上述程序的第29行和第30行中,使用语法p.city
和p.state
来访问晋升字段city
和state
,就像它们在结构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
。
在 computer
目录中,创建一个文件 spec.go
,内容如下:
1 2 3 4 5 6 7 8 package computertype Spec struct { Maker string Price int model string }
上述片段创建了一个包 computer
,其中包含一个导出的结构类型 Spec
,有两个导出的字段 Maker
和 Price
以及一个未导出的字段 model
。让我们从主包中导入这个包并使用Spec
结构。
在structs
目录下创建一个名为main.go
的文件,在main.go中编写以下程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "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结构中的两个导出字段Maker
和Price
。通过执行go install和
structs`命令,可以运行本程序。如果您不清楚如何运行Go程序,请访问https://golangbot.com/hello-world-gomod/#1goinstall,了解更多信息。
运行上述命令将打印:
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 mainimport ( "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 mainimport ( "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
类型的结构变量。
在上述程序中,name1
和name2
是相等的,而name3
和name4
则不是。这个程序将输出:
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 mainimport ( "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
是不能比较的,因此image1
和image2
不能被比较。如果你运行这个程序,编译将失败,并出现错误:
1 ./prog.go:20:12: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)