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 值本身呢?

所有这些并不是说你总应该使用指针。这章末尾,在我们见识了结构的更多功能以后,我们将重新检视 指针与值这个问题。

热门教程

最新教程