结构体

Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性。

  1. 一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行
  2. type Rect struct {
  3. x, y float64
  4. width, height float64
  5. }
  6. //初始化
  7. rect1 := new(Rect)
  8. rect2 := &Rect{}
  9. rect3 := &Rect{0, 0, 100, 200}
  10. rect4 := &Rect{width: 100, height: 200}
  11. //在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名,表示“构造函数”:
  12. func NewRect(x, y, width, height float64) *Rect {
  13. return &Rect{x, y, width, height}
  14. }

结构体成员和方法首字母大写时,外部可见,否则只有内部可使用,需要注意的一点是,Go语言中符号的可访问性是包一级的而不是类型一级的。

方法

在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

  1. type Point struct{ X, Y float64 }
  2. // traditional function
  3. func Distance(p, q Point) float64 {
  4. return math.Hypot(q.X-p.X, q.Y-p.Y)
  5. }
  6. // same thing, but as a method of the Point type
  7. func (p Point) Distance(q Point) float64 {
  8. return math.Hypot(q.X-p.X, q.Y-p.Y)
  9. }

上面的代码里那个附加的参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一个方法称为“向一个对象发送消息”。在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和简短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。

  • 为类型添加方法

在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法

  1. type Integer int
  2. func (a Integer) Less(b Integer) bool {
  3. return a < b
  4. }

我们定义了一个新类型Integer,它和int没有本质不同,只是它为内置的int类型增加了个新方法Less()。

  • 在Go语言中没有隐藏的this指针

方法施加的目标(也就是“对象”)显式传递,没有被隐藏起来;
方法施加的目标(也就是“对象”)不需要非得是指针,也不用非得叫this。

  • 对象的方法调用相当于普通函数调用的一个语法糖衣。

image.png


匿名组合

确切地说,Go语言也提供了继承,但是采用了组合的文法,所以我们将其称为匿名组合。

  1. type Base struct {
  2. Name string
  3. }
  4. func (base *Base) Foo() { ... }
  5. func (base *Base) Bar() { ... }
  6. type Foo struct {
  7. Base
  8. ...
  9. }
  10. func (foo *Foo) Bar() {
  11. foo.Base.Bar()
  12. ...
  13. }

以上代码定义了一个Base类(实现了Foo()和Bar()两个成员方法),然后定义了一个Foo类,该类从Base类“继承”并改写了Bar()方法(该方法实现时先调用了基类的Bar()方法)。
在“派生类”Foo没有改写“基类”Base的成员方法时,相应的方法就被“继承”,例如在上面的例子中,调用foo.Foo()和调用foo.Base.Foo()效果一致。
与其他语言不同,Go语言很清晰地告诉你类的内存布局是怎样的。此外,在Go语言中你还
可以随心所欲地修改内存布局,如:

  1. type Foo struct {
  2. ... // 其他成员
  3. Base
  4. }

这段代码从语义上来说,和上面给的例子并无不同,但内存布局发生了改变。“基类”Base的数据放在了“派生类”Foo的最后。

  1. //另外,在Go语言中,你还可以以指针方式从一个类型“派生”:
  2. type Foo struct {
  3. *Base
  4. ...
  5. }
  6. //这段Go代码仍然有“派生”的效果,只是Foo创建实例的时候,需要外部提供一个Base类实例的指针。

这段Go代码仍然有“派生”的效果,只是Foo创建实例的时候,需要外部提供一个Base类实例的指针。在C++ 语言中其实也有类似的功能,那就是虚基类,但是它非常让人难以理解,一般C++的开发者都会遗忘这个特性。相比之下,Go语言以一种非常容易理解的方式提供了一些原本期望用虚基类才能解决的设计难题。
匿名组合类型相当于以其类型名称(去掉包名部分)作为成员变量的名字,如果存在两个相同名字的成员,就可能收到编译错误(这两成员没有被使用的话就没有错误)。