什么是结构体
Golang中结构体用来将若干个域 整合在一起,是一自定义类型。当我们需要将多个数据字段整合成在一起整体使用时可以使用结构体。
例如,一个公司员工一般具有firstName
,lastName
以及age
等属性。因此把这三个属性放到一个结构体employee
内部来整体考虑比较好。
声明结构体
Copy type Employee struct {
firstName string
lastName string
age int
}
这里声明了一个结构体类型Employee
,该结构体类型具有三个域分别是firstName
、lastName
以及age
。每个域都有其对应的数据类型,如果两个域对应的数据类型相同,那么可以更加紧凑的声明在一行,比如这里的firstName
和lastName
都是string
类型,因此可以声明如下:
Copy type Employee struct {
firstName, lastName string
age int
}
由于这里新创建了一个数据类型Employee
,因此我们称这里声明的结构体Employee
为具名结构体 。注意这里的Employee
是一种结构体数据类型 。我们可以声明该类型的结构体变量:
Copy var employee Employee
当然,我们也可以基于匿名结构体 来声明结构体变量:
Copy var employee struct {
firstName, lastName string
age int
}
这里我们使用匿名结构体来声明了结构体变量employee
。
创建结构体变量
首先我们来创建一个具名结构体 变量。
Copy package main
import "fmt"
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp1 := Employee{
firstName: "Sam",
age: 25,
salary: 500,
lastName: "Anderson",
}
emp2 := Employee{"Thomas", "Paul", 29, 800}
fmt.Println("Employee1", emp1)
fmt.Println("Employee2", emp2)
}
Go Playground在线运行 这里我们声明了一个具名结构体类型Employee
,然后定义了该类型的结构体变量emp1
,emp1
定义时显示的指明了结构体各个域名,采用这种方式定义结构体变量无需保持各个域的顺序,比如这里我们就将域lastName
放到了最后。
接着我们定了另外一个结构体变量emp2
,注意定义时隐掉了各个域的名称,因此需要保持各个域的顺序。
该程序执行结果如下:
Copy Employee1 {Sam Anderson 25 500}
Employee2 {Thomas Paul 29 800}
我们再来创建一个匿名结构体 变量。
Copy package main
import "fmt"
func main() {
emp3 := struct {
firstName, lastName string
age, salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee3", emp3)
}
Go Playground在线运行 这里我们声明了一个匿名结构体变量emp3
,这里我们并未声明一个新的结构体类型,而是通过如下匿名结构体类型创建了一个匿名结构体变量:
Copy struct {
firstName, lastName string
age, salary int
}
该程序执行结果如下:
Copy Employee3 {Andreah Nikola 31 5000}
结构体变量的零值
如果我们声明了一个结构体变量,但是并不进行初始化赋值,那么该结构体变量的每个域将初始化为对应类型的零值。
Copy package main
import "fmt"
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
var emp4 Employee
fmt.Println("Employee4", emp4)
}
Go Playground在线运行 这里声明了结构体变量emp4
,但是并未进行初始化赋值。因此其各个域初始化为对应类型的零值。firstName
和lastName
初始化为string
类型的零值""
,age
和salary
初始化为int
类型的零值0
。该程序输出为:
我们也可以在声明结构体变量时初始化部分域,这时忽略掉的其他域初始化为对应类型的零值。
Copy package main
import "fmt"
type Employee struct {
firstName, lastName string
age, salary int
}
func main() {
emp5 := Employee{
firstName: "John",
lastName: "Paul",
}
fmt.Println("Employee5", emp5)
}
Go Playground在线运行 这里我们声明结构体变量emp5
时,仅初始化了域firstName
和lastName
,这时被忽略的域age
和salary
则初始化为int
类型的零值0
。该程序输出如下:
Copy Employee5 {John Paul 0 0}
访问结构体变量的域
Golang中通过.
运算符来访问结构体变量的某个域。
Copy 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)
}
Go Playground在线运行 这里通过emp6.firstName
来访问结构体emp6
的域firstName
。该程序输出如下:
Copy First Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000
我们也可以通过该方式来给结构体变量某个域赋值:
Copy 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("Employee7", emp7)
}
Go Playground在线运行 这里先声明了结构体变量emp7
,但是并未进行人工初始化,因此各个域均为零值。接着我们设置了firstName
和lastName
的值。该程序输出如下:
Copy Employee7 {Jack Adams 0 0}
指向结构体变量的指针
我们可以创建指向结构体变量的指针。
Copy 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)
}
Go Playground在线运行 这里的emp8
是一个指向结构体变量的指针,指向的结构体变量类型为Employee
。通过(*emp8).firstName
可以访问该指针指向的结构体变量的属性firstName
。该程序输出为:
Copy First Name: Sam
Age: 55
前面(*emp8).firstName
中首先对指针emp8
进行了解引用*emp8
,Golang允许我们简化成emp8.firstName
,这种方式更加简洁:
Copy 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)
}
Go Playground在线运行 该程序执行结果跟上一段程序保持一致。
匿名属性
我们创建的结构体类型中某些属性可以不指明属性名,仅指明其数据类型,结构体的这种属性称为匿名属性 。下面我们创建一个结构体类型Person
,其中含有两个匿名属性string
和int
。
Copy type Person struct {
string
int
}
我们通过一段程序来看下匿名属性的用法。
Copy package main
import "fmt"
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
Go Playground在线运行 这里结构体类型Person
含有两个匿名属性,p := Person{"Naveen", 50}
定义了一个该结构体类型的变量。该程序输出{Naveen 50}
。
尽快结构体的匿名属性不需要提供属性名,Golang中会默认使用其数据类型作为属性名 。例如前面的结构体类型Person
的两个匿名属性的属性名分别是string
和int
。
Copy package main
import "fmt"
type Person struct {
string
int
}
func main() {
var p1 Person
p1.string = "Naveen"
p1.int = 50
fmt.Println(p1)
}
Go Playground在线运行 这里我们通过匿名属性的数据类型作为属性名来访问对应的属性string
和int
。该程序输出如下:
结构体嵌套
Golang中,结构体中的某个属性仍然可以是结构体,称这种结构体为嵌套结构体
。
Copy 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)
}
Go Playground在线运行 这里结构体类型Person
中的属性address
仍然是结构体类型。该程序输出为:
Copy Name: Naveen
Age: 50
City: Chicago
State: Illinois
在嵌套结构体中,如果被嵌套的子结构体是个匿名属性,那么被嵌套的子结构体内部的属性会提升一层,这些被提升的属性看起来就像是父结构体自身属性一样。我们通过一个例子来理解下:
Copy type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
这里结构体类型Person
含有一个属性Address
是个匿名结构体属性。结构体Address
的两个属性city
和state
可以通过父结构体Person
直接访问,我们称Address
的这两个属性称为提升属性
。
Copy 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
}
Go Playground在线运行 这里我们直接通过父结构体p
来访问子结构体Address
的属性city
和state
,看起来就好像这两个属性是父结构体p
本身的属性一样。该程序输出为:
Copy Name: Naveen
Age: 50
City: Chicago
State: Illinois
结构体类型导出、结构体类型属性导出
如果一个结构体类型名称以大写字母开头,那么该结构体类型是一种导出类型 ,导出类型可以被其他包访问。类似的,如果结构体类型的属性名称以大写字母开头,这个属性也可以被其他包访问。下面我们通过一个例子来理解下。
在Golang工作目录的src
文件夹下面创建一个子文件夹structs
用来存放我们的项目。然后在structs
文件夹下再继续创建一个子文件夹computer
。 在computer
目录下创建文件spec.go
,并输入如下代码:
Copy package computer
type Spec struct { // 导出结构体类型供其他包访问
Maker string // 导出属性供其他包访问
model string // 非导出属性
Price int // 导出属性供其他包访问
}
这里创建了一个包computer
,导出了一个结构体类型Spec
及其两个属性Maker
和Price
供其他包访问,另外一个属性model
不是大写字母开头,因此未导出。下面我们在main
包中引入包computer
并使用。
在文件夹structs
下面创建文件main.go
,并输入如下代码:
Copy package main
import "structs/computer"
import "fmt"
func main() {
var spec computer.Spec
spec.Maker = "apple"
spec.Price = 50000
fmt.Println("Spec:", spec)
}
此刻我们创建的文件目录应该如下:
Copy src
structs
computer
spec.go
main.go
前面的程序中我们引入了包computer
,并且访问了其导出的结构体类型Spec
及其导出的属性Maker
和Price
。我们可以通过如下命令来执行该程序:
Copy go install structs
workspacepath/bin/structs
该程序输出如下:
如果我们尝试访问未导出的属性model
,Golang编译器会报错:
Copy 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)
}
这里我们访问了未导出的属性字段model
。执行会发现编译器报错:
Copy spec.model undefined (cannot refer to unexported field or method model)
判等
Golang中结构体是值类型(注意不是引用类型),如果两个结构体的所有属性类型兼容,那么这两个结构体就可以比较是否相等。当两个结构体的所有属性相等时这两个结构体也相等。
Copy package main
import "fmt"
type name struct {
firstName, 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")
}
}
Go Playground在线运行 这里结构体变量name1
和name2
的属性均为string
,string
类型是可比较的,因此这两个结构体变量也是可比较的。输出如下:
Copy name1 and name2 are equal
name3 and name4 are not equal
如果结构体变量的属性不可比较,比如 map
,那么该结构体也是不可比较的。
Copy 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")
}
}
Go Playground在线运行 这里结构体类型image
的属性data
是map
类型,而Golang中map
类型本身是不可比较的(这点可以回顾下前面介map的部分),因此会报错:
Copy invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)