本文始發於個人公眾號: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 排版