1. 程式人生 > >Golang簡易入門教程——面向物件篇

Golang簡易入門教程——面向物件篇

本文始發於個人公眾號:**TechFlow**,原創不易,求個關注

今天是golang專題的第9篇文章,我們一起來看看golang當中的面向物件的部分。

在現在高階語言當中,面向物件幾乎是不可或缺也是一門語言最重要的部分之一。golang作為一門剛剛誕生十年的新興語言自然是支援面向物件的,但是golang當中面向物件的概念和特性與我們之前熟悉的大部分語言都不盡相同。比如Java、Python等,相比之下, golang這個部分的設計非常得簡潔和優雅(仁者見仁),所以即使你之前沒有系統地瞭解過面向物件,也沒有關係,也一定能夠看懂。

常見的面向物件的部分,比如繼承、建構函式、解構函式,這些內容在golang當中統統沒有,因此整體的學習成本和其他的語言比起來會更低一些。

struct

在golang當中沒有類的概念,代替的是結構體(struct)這個概念。我們可以給結構體型別定義方法,為了表明該方法的適用物件是當前結構體,我們需要在方法當中定義接收者,位於func關鍵字和方法名之間。

我們一起來看一個例子:

type Point struct {
 x int
 y int
}

func (p Point) Dis() float64 {
 return math.Sqrt(float64(p.x*p.x + p.y*p.y))
}

在上面這段程式碼當中我們定義了一個叫做Point的結構體,以及一個面向這個結構體的方法Dis。我們一個一個來看它們的語法。

對於結構體來說,我們通過type關鍵字定義。在golang當中type關鍵字的含義是定義一個新的型別。比如我們也可以這樣使用type:

type Integer int

它的含義是從int型別定義了一個新的型別Integer,從此之後我們可以在後序的程式碼當中使用Integer來代替int。它有些類似於C++當中的typedef,結合這個含義,我們再來看結構體的定義就很好理解了。其實是我們通過struct關鍵字構造了一個結構體,然後使用type關鍵字定義成了一個型別。

之後我們建立了一個面向結構體Point的函式Dis,這個函式和我們之前使用的函式看起來並沒有太多的不同,唯一的區別在於我們在func和函式名之間多了一個(p Point)的定義。這其實是定義這個函式的接收者,也就是說它接受一個結構體的呼叫。

不僅如此,我們可以給golang當中的任何型別新增方法,比如:

type Integer int

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

在這個例子當中,我們給原生的int型別添加了Less這個方法,用來比較大小。我們在新增方法之前使用type給int起了一個別名,這是因為golang不允許給簡單的內建型別新增方法,並且接收者的型別定義和方法宣告必須在同一個包裡,我們必須要使用type關鍵字臨時定義一個新的型別。這裡要注意的是,雖然我們定義出來的Integer和int的功能完全一樣,但是它們屬於不同的型別,不能互相賦值。

和別的語言比較起來,這樣的定義的一個好處就是清晰。舉個例子,比如在Java當中,同樣的功能會寫成不同的樣子:

class Integer {
    private int val;
    public boolean less(Integer b) {
        return this.val < b.val;
    }
}

對於初學者而言,可能會覺得困惑,less函式當中的這個this究竟是哪裡來的?其實這是因為Java的成員方法當中隱藏了this這個引數,這一點在Python當中要稍稍清晰一些,因為它將self引數明確地寫了出來:

class Integer:
    def __init__(self, val):
        self.val = val
    def less(self, val):
        return self.val < val.val

而golang明確了結構體函式的接收者以及引數,顯得更加清晰。

指標接收者

golang當中,我們也可以將函式的接收者定義成指標型別。

比如我們可以將剛才的函式寫成這樣:

type Point struct {
 x int
 y int
}

func (p *Point) Dis() float64 {
 return math.Sqrt(float64(p.x*p.x + p.y*p.y))
}

指標接收者和型別接收者在使用上是一樣的,我們並不需要將結構體轉化成指標型別,可以直接進行呼叫。golang內部會自己完成這個轉化:

func main() {
 p := Point{3, 4}
 fmt.Print(p.Dis())
}

那麼這兩者的區別是什麼呢?我們既然可以定義成普通的結構體物件,為什麼還要有一個指標物件的接收者呢?

其實很好理解, 兩者的區別有些類似於C++當中的值傳遞和引用傳遞。在值傳遞當中,我們傳遞的是值的一個拷貝,我們在函式當中修改引數並不會影響函式外的結果。而引用傳遞則不然,傳遞的是引數的引用,我們在函式內部修改它的話,會影響函式外的值。

也就是說在golang當中,如果我們函式接收的是一個指標型別,我們可以在函式內部修改這個結構體的值。否則的話,傳入的是一個拷貝,我們在其中修改值並不會影響它本身。我們來看個例子:

func (p *Point) Modify() {
 p.x += 5
 p.y -= 3
}

func main() {
 p := Point{3, 4}
 p.Modify()
 fmt.Print(p)
}

上面這段程式碼當中函式的接收者是一個指標,所以我們得到的結果會是{8, 1},如果我們把指標去掉,改成普通的值接收的話,那麼最後的結果仍然是{3, 4}。

總結

我們今天學的內容有些多,我們來簡單梳理一下。首先,我們瞭解了通過type和struct關鍵字來定義一個結構體,結構體是golang當中面向物件的載體,golang拋棄了傳統的面向物件的實現方式和特性,擁有自己的面向物件的理念。

對於結構體來說,我們可以把它當做是接受者傳遞給一個函式,使得我們可以以類似呼叫類當中方法的形式來呼叫一個函式。並且對於函式而言,接受者除了值以外還可以是一個指標。如果是指標的話,當我們對結構體值進行修改的時候,會影響到原值。即使我們定義的接收者型別是指標,我們在呼叫的時候也不必顯示將它轉化成結構體指標,golang當中會自動替我們完成這樣的轉化。

面向物件部分可以說是golang這一門語言當中最大的創新之一,也正是因為拋棄了傳統的類以及繼承、派生的概念,使得golang當中的面向物件語法糖相對簡潔。也因此有人將golang稱為升級版的C語言。雖然我們囉囉嗦嗦寫了很多,但是實際談到的內容並不多,我想理解起來也不會特別困難。

今天的文章到這裡就結束了,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

![](https://user-gold-cdn.xitu.io/2020/6/22/172d9d6c9e016392?w=258&h=258&f=png&