1. 程式人生 > >Go語言學習筆記(七) 系統型別

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的內部方法,但同一個包中的其他型別也都可以訪問到它。這樣的可訪問性控制很粗曠,很特別,但是非常實用。