结构体是用户定义的类型,表示字段的集合。它可以用在需要将数据分组为单个单元而不是将每个数据作为单独值的地方。
例如,员工有名字、姓氏和年龄。将这三个属性分组到一个名为Employee
的结构中是有意义的。
type Employee struct {
firstName string
lastName string
age int
}
上面的代码片段声明了一个带有 firstName
、lastName
和age
的结构类型。上面的Employee
结构称为命名结构,因为它创建了一个名为Employee
的新数据类型,可以使用该数据类型来创建结构
通过在一行中声明属于同一类型的字段并后跟类型名称,还可以使该结构更加紧凑。在上面的结构体中firstName
和lastName
属于同一类型string
,因此该结构体可以重写为
type Employee struct {
firstName, lastName string
age int
}
虽然上面的语法节省了几行代码,但它并没有使字段声明变得明确。请避免使用上述语法。
让我们使用以下简单程序声明一个Employee 的 struct 。
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)
}
Run in playground
在上面程序的第 7 行中,我们创建了一个Employee
struct type 。
在上述程序的第 17 行中,emp1
通过指定每个字段名称的值来定义结构体。字段的顺序不必与声明结构类型时字段名称的顺序相同。
在这种情况下。我们改变了位置lastName
并将其移到最后。这将毫无问题地工作。
在上述程序的第 25 行中,emp2
通过省略字段名称来定义。在这种情况下,有必要保持字段的顺序与结构声明中指定的顺序相同。
请避免使用此语法,因为它会导致很难确定哪个值对应哪个字段。
我们在这里指出这种格式只是为了了解这也是一种有效的语法
上面的程序打印出
Employee 1 {Sam Anderson 25 500}
Employee 2 {Thomas Paul 29 800}
可以在不创建新数据类型的情况下声明结构。这些类型的结构称为匿名结构。
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)
}
Run in playground
在上述程序的第 8 行中,定义了一个匿名结构变量。 emp3
正如我们已经提到的,这个结构体被称为匿名结构体,因为它只创建一个新的结构体变量,并且没有像命名结构体那样定义任何新的结构体类型。
该程序输出,
Employee 3 {Andreah Nikola 31 5000}
点.
运算符用于访问结构体的各个字段。
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)
}
Run in playground
上面程序中的emp6.firstName 是访问emp6
结构体的firstName
字段。在25 行号中。我们修改员工的工资。该程序打印,
First Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000
New Salary: $6500
当定义一个结构体并且未使用任何值显式初始化它时,默认情况下该结构体的字段将被分配零值。
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)
}
Run in playground
上面的程序定义了emp4
但没有用任何值初始化。因此firstName
和lastName
被分配了string的零值(空字符串)""
,age
和salary
被分配了 int 的零值,即 0。
该程序打印,
First Name:
Last Name:
Age: 0
Salary: 0
也可以指定某些字段的值并忽略其余字段。在这种情况下,被忽略的字段被分配零值。
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)
}
Run in playground
在上面的程序中,firstName
和lastName
已初始化,而age
和salary
未初始化。因此age
和salary
被赋予零值。该程序输出,
First Name: John
Last Name: Paul
Age: 0
Salary: 0
也可以创建指向结构的指针。
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)
}
Run in playground
上面程序中的emp8是指向Employee
结构体的指针。访问结构体字段的语法:(*emp8).firstName
、(*emp8).age
该程序打印,
First Name: Sam
Age: 55
Go 语言为我们提供了使用emp8.firstName
而不是显式取消引用(\*emp8).firstName
来访问firstName
字段的选项。
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)
}
Run in playground
我们在上面的程序中使用emp8.firstName
过访问该字段,并且该程序还输出
First Name: Sam
Age: 55
可以创建包含仅包含类型而不包含字段名称的字段的结构。此类字段称为匿名字段。
下面的代码片段创建了一个Person
结构体,具有两个匿名字段的string
和int
type Person struct {
string
int
}
即使匿名字段没有显式名称,默认情况下匿名字段的名称就是其类型的名称。
例如,在上面的 Person 结构体中,虽然字段是匿名的,但默认情况下它们采用字段类型的名称。所以Person
struct 有 2 个字段,分别为 string
和int
。
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)
}
Run in playground
上述程序中, 我们使用其类型作为字段名称来访问 Person 结构体的匿名字段,分别为string
和int
。上述程序的输出是,
naveen
50
结构体可能包含一个字段,而该字段又是一个结构体。这些类型的结构称为嵌套结构。
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)
}
Run in playground
Person
上面程序中的结构体有一个字段,而address
该字段又是一个结构体。该程序打印
Name: Naveen
Age: 50
City: Chicago
State: Illinois
属于结构中匿名结构字段的字段被称为Promoted fields,因为它们可以被访问,就好像它们属于包含匿名结构域的结构一样。
我可以理解这个定义非常复杂,所以让我们直接深入一些代码来理解这一点
type Address struct {
city string
state string
}
type Person struct {
name string
age int
Address
}
在上面的代码片段中,Person
结构体有一个匿名字段Address
,它是一个结构体。
现在Address
的字段被称为Promoted fields,因为它们可以像直接在结构本身中声明一样被访问结构体本身的字段city
、state
、Person
。
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
}
Run in playground
在上面程序的第 29 和 30行,访问提升的字段city
和state
就好像它们在结构体中声明的一样,使用语法p.city
和p.state
。该程序打印,
Name: Naveen
Age: 50
City: Chicago
State: Illinois
如果结构类型以大写字母开头,那么它是导出类型并且可以从其他包访问。同样,如果结构体的字段以 大写字母开头,则可以从其他包访问它们。
让我们编写一个具有自定义包的程序来更好地理解这一点。
在目录中,创建一个learnpackage
包含以下内容的文件。
package computer
type Spec struct { //exported struct
Maker string //exported field
Price int //exported field
model string //unexported field
}
上面的代码片段创建了一个包 computer
,其中包含一个导出的结构类型Spec
,其中包含两个导出字段Maker
和Price
一个未导出字段model
。
让我们从主包导入这个包并使用该Spec
结构。
在目录中创建一个名为的main.go
文件,并在其中写入以下程序
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)
}
该structs
文件夹应具有以下结构,
├── structs
│ ├── computer
│ │ └── spec.go
│ ├── go.mod
│ └── main.go
上面的程序,我们导入computer
包。我们访问struct 的两个导出字段和Maker
。
运行程序:
Maker: apple
Price: 50000
如果我们尝试访问未导出的字段model
,编译器会报错。我们把代码替换成下面的:
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
。运行该程序将导致编译错误。
# structs
./main.go:12:13: unknown field 'model' in struct literal of type computer.Spec
由于model
该字段未导出,因此无法从其他包访问它。
结构是值类型,如果每个字段都可比较,则结构也可比较。如果两个结构体变量对应的字段相等,则认为它们相等。
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")
}
}
Run in playground
在上面的程序中,name
结构体类型包含两个string
字段。由于字符串是可比较的,因此可以比较两个类型的结构变量。
在上面的程序中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")
}
}
Run in playground
在上面的程序中, image struct type
包含一个类型为map
,不具有可比性,因此也无法进行比较。如果运行该程序,编译将失败并出现错误
./prog.go:20:12: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)