结构体
本文我们着重讲解一下Go语言中的结构体。
什么是结构体 ?
结构体是用户定义的数据类型,它表示了一组字段的集合。当我们需要把一组数据汇集成单一的逻辑单元而不是把这些数据各自处理成单独的类型。例如,雇员有姓氏、名称、年龄,那我们就可以把这三个字段构造成一个单独的结构体employee。
声明一个结构体
type Employee struct{
firstName string
lastName string
age int
}
上面的代码声明了一个结构体类型Employee,它有firstName,lastName, age这三个字段。这个结构体还可以更紧凑一些,我们可以把相同类型的字段放在同一行代码里面声明。例如,firstName和lastName都属于string类型,因此我们可以把上面的程序写成如下:
type Employee struct{
firstName,lastName string
age, salary int
}
上面的Employee结构体,我们称它为命名结构体,因为它创建了一个名为Employee的新类型。今后我们就可以使用Employee这个新类型创建一个Employee类型的结构体。
除此之外,在声明结构体时,还可以不声明一个新类型,这种类型的结构体被称为匿名结构体。
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)
}
在上面的程序中,我们定义了一个结构体类型Employee。我们又定义了一个结构体变量emp1,给它的每个字段都赋予了值。像这样的情况,字段名称的顺序没必要一定要和结构体声明时的顺序保持一致。
同时,我们还定义了一个结构体变量emp2,在定义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)
}
在上面的程序中,我们定义了一个匿名结构体变量emp3。像我们之前提到的,这种结构体类型称为匿名结构体。因为它仅创建了一个结构体变量emp3,而并没有定义任何的新结构体类型。
程序的输出如下:
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,并且没有初始化任何值。因此,firstName和lastName就会被赋予字符串的零值即:“”。而age和salary则会被赋予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)
}
程序的输出如下:
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,之后又为它的firstName和lastName字段赋予值。程序的输出如下:
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是一个指向结构体变量的指针。(*emp8).firstName是用于访问emp8结构体的firstName字段。
程序的输出如下:
First Name: Sam
Age: 55
除了使用(*emp8).firstName访问firstName字段外,还可以使用emp8.firstName来访问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)
}
输出结果如下:
First Name: Sam
Age: 55
匿名字段
在创建结构体时,我们也可以只指定类型而不指定字段名。这些字段被称为匿名字段。
下面的代码片就创建了一个Person,该结构体有俩个匿名字段string和int。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
p := Person{"Naveen", 50}
fmt.Println(p)
}
程序的输出如下:
{Naveen 50}.
尽管匿名字段并没有名称,默认情况下,它的数据类型就是它的字段名称。例如Person结构体,虽然字段是匿名的,但是默认地它会拿字段的类型作为名称。因此,Person结构体拥有俩个字段,其名称分别为:string和int。
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
var p1 Person
p1.string = "naveen"
p1.int = 50
fmt.Println(p1)
}
在上面的程序中,我们分别使用字段的数据类型作为它的字段名称来访问字段,程序的输出结果为:
{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
结构体中的匿名的结构体字段被称为promoted字段,我们用一段代码来理解下。
type Address struct {
city, state string
}
type Person struct {
name string
age int
Address
}
在上面的代码片段中,Person结构体有一个匿名字段Address,Address同时也是一个结构体类型。现在Address结构体中的字段city和state被称为promoted字段,因为它们可以直接访问,就好像它们是直接在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
}
在上面的程序中,city和state是promoted字段,它们可以直接使用p.city和p.state访问,就像它们直接是在结构体p中声明一样。程序的输出如下:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
导出结构体和字段
如果一个结构体类型以大写字母开头的话,那么它就是一个导出类型,导出类型可以在其他包中访问。相似地,如果结构体的字段以大写字母开头的话,他们可以在其他包中访问。
我们来写一个代码,理解这个。
在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,Spec里面有俩个导出类型Maker和Price和一个非导出字段model。我们在main包里面引入此包,并使用Spec结构体。
在structs目录下,创建一个main.go文件,并把下面的程序写入mian.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
在上面的程序中,我们引入了computer包。同时我们访问了Spec结构体的导出字段Maker以及Price。
在workspacepath/bin/structs下面执行go install 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)
}
上面的程序中,报错如下: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类型的结构体变量可以做比较。
在上面程序中,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")
}
}
在上述程序中,image结构体包含了一个map类型的字段data。由于map是不可比的,所以image1和image2也是不可比的。如果你运行此程序的话,编译器会报错:main.go:18: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared).
源代码,请查看github 代码地址.
以上就是关于结构体的所有内容,感谢您的阅读。