Go语言学习笔记(七) 系统类型

系统类型

对于面向对象编程的支持Go 语言设计得非常简洁而优雅。简洁之处在于,Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承、虚函数、构造函数和析构函数、隐藏的this指针等。优雅之处在于,Go语言对面向对象编程的支持是语言类型系统中的天然组成部分。整个类型系统通过接口串联,浑然一体。

在Java语言中,存在两套完全独立的类型系统:一套是值类型系统,主要是基本类型,如byte、int、boolean、char、double等,这些类型基于值语义;一套是以Object类型为根的对象类型系统,这些类型可以定义成员变量和成员方法,可以有虚函数,基于引用语义,只允许在堆上创建(通过使用关键字new)。

Java语言中的Any类型就是整个对象类型系统的根——java.lang.Object类型,只有对象类型系统中的实例才可以被Any类型引用。值类型想要被Any类型引用,需要装箱(boxing)过程,比如int类型需要装箱成为Integer类型。另外,只有对象类型系统中的类型才可以实现接口,具体方法是让该类型从要实现的接口继承。相比之下,Go语言中的大多数类型都是值语义,并且都可以包含对应的操作方法。在需要的时候,你可以给任何类型(包括内置类型)“增加”新方法。而在实现某个接口时,无需从该接口继承(事实上,Go语言根本就不支持面向对象思想中的继承语法),只需要实现该接口要求的所有方法即可。任何类型都可以被Any类型引用。Any类型就是空接口,即interface{}。

你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法,它和int没有本质不同,只是它为内置的int类型增加了个新方法Less()。

type Integer int
func (a Integer) Less(b Integer) bool {
    return a < b
}

“在Go语言中没有隐藏的this指针”这句话的含义是:

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

为Integer类型增加了Add()方法。由于Add()方法需要修改对象的值,所以需要用指针引用。

func (a *Integer) Add(b Integer) {
    *a += b
}

Integer类型其实就是一个int,但通过为int起一个Integer别名并增加了一系列方法,它就变成了一个全新的类型,但这个新类型又完全拥有int的功能。

值语义和引用语义

Go语言中的数组和基本类型没有区别,是很纯粹的值类型,例如:

var a = [3]int{1, 2, 3}
var b = a
b[1]++
fmt.Println(a, b)

该程序的运行结果如下:

[1 2 3] [1 3 3]

这表明b=a赋值语句是数组内容的完整复制。要想表达引用,需要用指针:

var a = [3]int{1, 2, 3}
var b = &a
b[1]++
fmt.Println(a, *b)

该程序的运行结果如下:

[1 3 3] [1 3 3]

这表明b=&a赋值语句是数组内容的引用。变量b的类型不是[3]int,而是*[3]int类型。

结构体

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

所有的Go语言类型(指针类型除外)都可以有自己的方法。在这个背景下,Go语言的结构体只是很普通的复合类型:

type Rect struct {
    x, y float64
    width, height float64
}
func (r *Rect) Area() float64 {
    return r.width * r.height
}

初始化

创建并初始化Rect类型的对象实例:

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

在Go语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如bool类型的零值为false,int类型的零值为0,string类型的零值为空字符串。在Go语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名,表示“构造函数”:

func NewRect(x, y, width, height float64) *Rect {
    return &Rect{x, y, width, height}
}

匿名组合

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

type Base struct {
    Name string
}
func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }
type Foo struct {
    Base
    ...
}
func (foo *Foo) Bar() {
    foo.Base.Bar()
    ...
}

以上代码定义了一个Base类(实现了Foo()和Bar()两个成员方法),然后定义了一个Foo类,该类从Base类“继承”并改写了Bar()方法(该方法实现时先调用了基类的Bar()方法)。在“派生类”Foo没有改写“基类”Base的成员方法时,相应的方法就被“继承”,例如在上面的例子中,调用foo.Foo()和调用foo.Base.Foo()效果一致。

可见性

要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头,如:

//Rect类型的成员变量就全部被导出了
type Rect struct {
    X, Y float64
    Width, Height float64
}
//Rect的area()方法只能在该类型所在的包内使用
func (r *Rect) area() float64 {
    return r.Width * r.Height
}

需要注意的一点是,Go语言中符号的可访问性是包一级的而不是类型一级的。在上面的例子中,尽管area()是Rect的内部方法,但同一个包中的其他类型也都可以访问到它。这样的可访问性控制很粗旷,很特别,但是非常实用。

 

你可能感兴趣的:(Go)