在 Go 中,struct 是一系列具名元素组成的序列,这些元素称为_字段_,每个字段都有名字和类型。
字段名在同一个结构体内必须唯一。
结构体可以类比面向对象编程范式中的_类_。
使用 type 和 struct 关键字可以创建新的结构体,并显式定义各字段的名字和类型。
例如,下面定义了一个Shape结构体,用来保存形状的名字和大小:
type Shape struct {
name string
size int
}
结构体中的字段名遵循 Go 的约定:以小写字母开头的字段只对同一个包中的代码可见,而以大写字母开头的字段对其他包也可见。
定义好struct之后,你需要创建新的实例。创建时用字段名来指定各个字段的值,顺序任意:
s := Shape {
name: "Square",
size: 25,
}
要读取或修改实例的字段,使用 . 记法:
// Update the name and the size
s.name = "Circle"
s.size = 35
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 35
没有赋初值的字段会取零值。 例如:
s := Shape{name: "Circle"}
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 0
只要按_顺序_定义字段,创建struct实例时就可以不使用字段名:
s := Shape {
"Oval",
20,
}
不过,这种写法被视为_脆弱代码_,因为一旦新增字段就可能失效,尤其是当新字段的类型不同时。
在下面的例子中,我们给Shape额外添加了一个字段:
type Shape struct {
name string
description string // new field 'description' added
size int
}
s := Shape{
"Circle",
20,
}
// Since the second field of the struct is now a string and not an int,
// the compiler will throw an error when compiling the program:
// Output: cannot use 20 (type untyped int) as type string in field value
// Output: too few values in Shape{...}
有时,有一些能帮我们创建结构体实例的函数会很方便。
按照惯例,这类函数通常命名为New,或者名字以New开头;不过它们只是普通函数,你可以随便取名。
它们可能会让你想起其他语言中的构造函数,但在 Go 里,它们只是普通函数。
在下面的例子中,我们用这样一个New函数创建Shape的新实例,并自动为形状的size设置默认值:
func NewShape(name string) Shape {
return Shape{
name: name,
size: 100, //default-value for size is 100
}
}
NewShape("Triangle")
// => Shape { name: "Triangle", size: 100 }
使用New函数有以下几个好处:
New函数通常与被初始化的结构体声明在同一个包中,因此它们甚至能初始化结构体的私有字段在这个练习中,你将组织各种类型遥控车之间的比赛。 每辆车都有自己的速度和电池消耗特性。
赛车的初始电池电量都是满的(100%)。 每次你使用遥控器驱动赛车时,它都会前进相当于该车速度的距离(以米为单位),并根据电池消耗减少剩余的电池百分比。
如果一辆车的电池电量低于它的电池消耗百分比,你就不能再驱动这辆车了。
每条赛道都有自己的距离。 检验赛车的方式,是看它们能否在电池耗尽前跑完整条赛道。
定义一个Car结构体,包含以下int类型的字段:
通过定义一个函数NewCar来创建遥控车,它接收两个形参:赛车的速度(以米为单位)和电池消耗百分比(都是int类型),并返回一个Car实例:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
// => Car{speed: 5, batteryDrain: 2, battery:100, distance: 0}
再定义一个结构体类型Track,它有一个整数类型的字段distance。
通过定义一个函数NewTrack来创建赛道,它唯一的形参是赛道的距离(以米为单位,类型为int):
distance := 800
track := NewTrack(distance)
// => Track{distance: 800}
实现Drive函数,根据赛车的速度更新已行驶的米数,并按电池消耗减少电量。
如果剩余电量不足以再驱动一次,赛车就不会移动:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
car = Drive(car)
// => Car{speed: 5, batteryDrain: 2, battery: 98, distance: 5}
要完成一场比赛,赛车必须能跑完比赛的距离。
这意味着在冲过终点线之前,它的电池不能耗尽。
实现CanFinish函数,它接收一个Car实例和一个Track实例作为形参,如果赛车能完成比赛就返回true;否则返回false。
假设你现在正位于比赛的起跑线上,并且已经为比赛发动了赛车引擎。 注意,比赛开始时赛车的电池未必是满的:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
distance := 100
track := NewTrack(distance)
CanFinish(car, track)
// => true