1.0 函數、方法和接口
- 具名函數一般對應於包級的函數,是匿名函數的一種特例,當匿名函數引用了外部作用域中的變量時就成了閉包函數閉包函數是函數式編程語言的核心。
- 方法是綁定到一個具體類型的特殊函數,Go語言中的方法是依託於類型的,必須在編譯時靜態綁定。
- 接口定義了方法的集合,這些方法依託於運行時的接口對象,因此接口對應的方法是在運行時動態綁定的。Go語言通過隱式接口機制實現了面向對象模型。
Go語言程序的初始化和執行總是從 main.main 函數開始的。但是如果 main 包導入了其它的包,則會按照順序將它們包含進main包裏。如果某個包被多次導入的話,在執行的時候只會導入一次。當一個包被導入時,如果它還導入了其它的包,則先將其它的包包含進來。
然後創建和初始化這個包的常量和變量,再調用包裏的 init 函數。同一個文件內的多個 init 則是以出現的順序依次調用( init 不是普通函數,可以定義有多個,所以也不能被其它函數調用)。
最後,當 main 包的所有包級常量、變量被創建和初始化完成,並且 init 函數被執行後,纔會進入 main.main 函數,程序開始正常執行。下圖是Go程序函數啓動順序的示意圖:
1|1函數
Go語言中的函數可以有多個參數和多個返回值,參數和返回值都是以傳值的方式和被調用者交換數據。在語法上,函數還支持可變數量的參數,可變數量的參數必須是最後出現的參數,可變數量的參數其實是一個切片類型的參數。
Go語言中,如果以切片爲參數調用函數時,有時候會給人一種參數採用了傳引用的方式的假象:因爲在被調用函數內部可以修改傳入的切片的元素。其實,任何可以通過函數參數修改調用參數的情形,都是因爲函數參數中顯式或隱式傳入了指針參數。因爲切片中的底層數組部分是通過隱式指針傳遞(指針本身依然是傳值的,但是指針指向的卻是同一份的數據),所以被調用函數是可以通過指針修改掉調用參數切片中的數據。除了數據之外,切片結構還包含了切片長度和切片容量信息,這2個信息也是傳值的。如果被調用函數中修改了 Len 或 Cap 信息的話,就無法反映到調用參數的切片中,這時候我們一般會通過返回修改後的切片來更新之前的切片。這也是爲何內置的append 必須要返回一個切片的原因。
Go語言函數的遞歸調用深度邏輯上沒有限制,函數調用的棧是不會出現溢出錯誤的,因爲Go語言運行時會根據需要動態地調整函數棧的大小。
Go1.4之後改用連續的動態棧實現,也就是採用一個類似動態數組的結構來表示棧。不過連續動態棧也帶來了新的
問題:當連續棧動態增長時,需要將之前的數據移動到新的內存空間,這會導致之前棧中全部變量的地址發生變化。
1|2方法
這樣的話, CloseFile 和 ReadFile 函數就成了 File 類型獨有的方法了(而不是 File 對象方法)。它們也不再佔用包級空間中的名字資源。對於給定的類型,每個方法的名字必須是唯一的,同時方法和函數一樣也不支持重載。
Go語言不支持傳統面向對象中的繼承特性,而是以自己特有的組合方式支持了方法的繼承。Go語言中,通過在結構體內置匿名的成員來實現繼承:
雖然我們可以將 ColoredPoint 定義爲一個有三個字段的扁平結構的結構體,但是我們這裏將 Point 嵌入到 ColoredPoint 來提供 X 和 Y 這兩個字段。所有繼承來的方法的接收者參數依然是那個匿名成員本身,而不是當前的變量。
1|3接口
Go語言中接口類型的獨特之處在於它是滿足隱式實現的鴨子類型。所謂鴨子類型說的是:只要走起路來像鴨子、叫起來也像鴨子,那麼就可以把它當作鴨子。Go語言中的面向對象就是如此,如果一個對象只要看起來像是某種接口類型的實現,那麼它就可以作爲該接口類型使用。