Go 不是像 C ++,Java,Ruby和C#一样的面向对象的(OO)语言。它没有对象和继承的概念,也没有很多与面向对象相关的概念,例如多态和重载。
Go所具有的是结构体的概念,可以将一些方法和结构体关联。Go 还支持一种简单但有效的组合形式。 总的来说,它会使代码变的更简单,但在某一些场合,你会错过面向对象提供的一些特性。(值得指出的是,通过组合实现继承是一场古老的战斗呐喊,Go 是我用过的第一种坚定立场的语言,在这个问题上。)
虽然 Go 不会像你以前使用的面向对象语言一样,但是你会注意到结构的定义和类的定义之间有很多相似之处。下面的代码定义了一个简单的 Saiyan
结构体:
type Saiyan struct {
Name string
Power int
}
我们将看明白怎么往这个结构体添加一个方法,就像面向对象类,会有方法作为 它的一部分。在这之前,我们先要知道如何声明结构体。
声明和初始化
当我们第一次看到变量和声明时,我们只看了内置类型,比如整数和字符串。既然现在我们要讨论结构,那么我们需要把讨论范围扩展到指针。
创建结构的值的最简单的方式是:
goku := Saiyan{
Name: "Goku",
Power: 9000,
}
注意: 上述结构末尾的逗号 ,
是必需的。没有它的话,编译器就会报错。你将会喜欢上这种必需的一致性,尤其当你使用一个与这种风格相反的语言或格式的时候。
我们不必设置所有或哪怕一个字段。下面这些都是有效的:
goku := Saiyan{}
// or
goku := Saiyan{Name: "Goku"}
goku.Power = 9000
就像未赋值的变量其值默认为 0 一样,字段也是如此。
此外,你可以不写字段名,依赖字段顺序去初始化结构体 (但是为了可读性,你应该把字段名写清楚):
goku := Saiyan{"Goku", 9000}
以上所有的示例都是声明变量 goku
并赋值。
许多时候,我们并不想让一个变量直接关联到值,而是让它的值为一个指针,通过指针关联到值。一个指针就是内存中的一个地址;指针的值就是实际值的地址。这是间接地获取值的方式。形象地来说,指针和实际值的关系就相当于房子和指向该房子的方向之间的关系。
为什么我们想要一个指针指向值而不是直接包含该值呢?这归结为 Go 中传递参数到函数的方式:镜像复制。知道了这个,尝试理解一下下面的代码呢?
func main() {
goku := Saiyan{"Power", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s Saiyan) {
s.Power += 10000
}
上面程序运行的结果是 9000,而不是 19000,。为什么?因为 Super
修改了原始值 goku
的复制版本,而不是它本身,所以,Super
中的修改并不影响上层调用者。现在为了达到你的期望,我们可以传递一个指针到函数中:
func main() {
goku := &Saiyan{"Power", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s *Saiyan) {
s.Power += 10000
}
这一次,我们修改了两处代码。第一个是使用了 &
操作符以获取值的地址(它就是 取地址 操作符)。然后,我们修改了 Super
参数期望的类型。它之前期望一个 Saiyan
类型,但是现在期望一个地址类型 *Saiyan
,这里 *X
意思是 指向类型 X 值的指针 。很显然类型 Saiyan
和 *Saiyan
是有关系的,但是他们是不同的类型。
这里注意到我们仍然传递了一个 goku
的值的副本给 Super
,但这时 goku
的值其实是一个地址。所以这个副本值也是一个与原值相等的地址,这就是我们间接传值的方式。想象一下,就像复制一个指向饭店的方向牌。你所拥有的是一个方向牌的副本,但是它仍然指向原来的饭店。
我们可以证实一下这是一个地址的副本,通过修改其指向的值(尽管这可能不是你真正想做的事情):
func main() {
goku := &Saiyan{"Power", 9000}
Super(goku)
fmt.Println(goku.Power)
}
func Super(s *Saiyan) {
s = &Saiyan{"Gohan", 1000}
}
上面的代码,又一次地输出 9000。就像许多语言表现的那样,包括 Ruby,Python, Java 和 C#,Go 以及部分的 C#,只是让这个事实变得更明显一些。
同样很明显的是,复制一个指针比复制一个复杂的结构的消耗小多了。在 64 位的机器上面,一个指针占据 64 bit 的空间。如果我们有一个包含很多字段的结构,创建它的副本将会是一个很昂贵的操作。指针的真正价值在于能够分享它所指向的值。我们是想让 Super
修改 goku
的副本还是修改共享的 goku
值本身呢?
所有这些并不是说你总应该使用指针。这章末尾,在我们见识了结构的更多功能以后,我们将重新检视 指针与值这个问题。