GO语言基础篇(十五)- 结构体详解

393 阅读4分钟

这是我参与8月更文挑战的第 15 天,活动详情查看: 8月更文挑战

结构体

结构体基础

结构体是将零个或者多个任意类型的命名变量组合在一起的聚合数据类型。每个变量都叫做结构体的成员

下边定义了一个叫Employee的结构体和一个结构体变量dilbert

type Employee struct {
    ID int
    Name string
    Address string
    Salary int
    Position string
}
var dilbert Employee

dilbert的每一个成员都通过点号来访问,比如dilbert.Name。因为dilbert是一个变量,它的所有成员都是变量,因此可以给结构体的成员赋值

dilbert.Salary -= 5000
//或者获取成员变量的地址,然后通过指针来访问
position := &dilbert.Position
*position = "Senior " + *position

//点号同样可以用在结构体指针上
var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += "(proactive team player)"

这里并不需要像C++中那样,需要在employeeOfTheMonth的前边加个*号

结构体的成员变量通常一行写一个,变量的名称在类型的前面,但是相同类型的连续成员变量可以写在一行上,比如下边的Name和Address

type Employee struct {
    ID int
    Name,Address string
    Salary int
    Position string
}

成员变量的顺序对于结构体同一性很重要。如果我们将也是字符串类型的Position和Name、Address组合在一起,或者互换了Name和Address的顺序,那么就相当于定义了一个不同的结构体类型

结构体不可以定义一个拥有和它本身相同结构体类型的成员变量,也就是说,一个聚合类型不可以包含它自己(数组也是这样)。但是结构体里边可以包含它本身类型的指针类型,如下:

type Node struct {
    Value int
    Left, Right *Node
}

结构体的零值由结构体成员的零值组成。没有任何成员变量的结构体称为空结构体,写作struct{}。它没有长度,也不携带任何信息

结构体字面量

结构体的值可以通过结构体字面量来设置

type Point struct {
    X, Y int
}
p := Point{1, 2}

这种初始化方式不友好,会给开发和阅读代码的人增加负担,因为他们必须记住每个成员变量的顺序,另外这也使得未来结构体成员变量扩充或者重新排列的时候增加负担。所以建议使用另一种方式

p := Point{X:1, Y:2}

如果在这种初始化方式中,某个成员变量没有指定,那么他的值就是该成员变量类型的零值

结构体类型的值可以作为参数传递给函数或者作为函数的返回值

func Scale(p Point, factor int) Point{
    return Point{p.X * factor, p.Y * factor}
}
fmt.Println(Scale(Point{1,2}, 5)) // "{5, 10}"

出于效率考虑,大型的结构体,通常都是使用结构体指针的方式直接传递给函数或从函数中返回(这种方式在函数需要修改结构体内容的时候,也是必须的)

func Bonus(e *Employee, percent int) int {
    return e.Salary * percent/100
}

结构体比较

如果结构体的所有成员变量都可以比较,那么这个结构体就是可以比较的

type Point struct {
    X, Y int
}
p := Point{1, 2}
q := Point{2, 1}
fmt.Println(p.X == q.X && p.Y == q.Y) // false
fmt.Println(p == q) // false

和其它类型一样,可比较的结构体类型,可以作为map的键

type address struct {
    hostname string
    port int
}
hits := make(map[address]int)
hits[address{"golang.org", 443}]++

结构体嵌套

假设现在定义了以下几个结构体

type Point struct {
    X, Y int
}
type Circle struct {
    Center Point
    Radius int
}
type Wheel struct {
    Circle Circle
    Spokes int
}

这个程序看上去很清晰,但是访问Wheel的成员变得麻烦了

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20

Go里边允许我们定义不带名称的结构体成员,只需要指定类型即可。这个结构体成员的类型必须是一个命名类型或者指向命名类型的指针

type Circle struct {
    Point
    Radius int
}
type Wheel struct {
    Circle
    Spokes int
}
//使用这种方式之后,访问嵌套的结构体的成员也变得方便了
var w Wheel
w.Y = 8 // 等价于w.Circle.Center.X = 8
w.X = 8 // 等价于w.Circle.Center.Y = 8
w.Radius = 5 // 等价于w.Circle.Radius = 5
w.Spokes = 20

结构体字面量没有什么快捷的方式来初始化这种嵌套的结构体,所以下边的语法是错误的

w = Wheel{8,8,5,20}//编译错误
w = Wheel{X:8,Y:8,Radius:5,Spokes:20}//编译错误

//需要这样
w = Wheel{
    Circle: Circle{
        Point: Point{X:8,Y:8},
        Radius: 5,
    },
    Spokes: 20,
}
//或者
w = Wheel{Circle{Point{8, 8}, 5}, 20}