【高階資料型別2】- 8. 結構體和方法
Go語言-結構體和方法
Go語言的結構體型別(Struct)比函式型別更加靈活。它可以封裝屬性和操作。前者即是結構體型別中的欄位,而後者則是結構體型別所擁有的方法。
結構體型別的字面量由關鍵字type
、型別名稱、關鍵字struct
,以及由花括號包裹的若干欄位宣告組成。其中,每個欄位宣告獨佔一行並由欄位名稱(可選)和欄位型別組成。示例如下:
type Person struct { Name string Gender string Age uint8 }
結構體型別Person
Name
、Gender
和Age
。我們可以用字面量創建出一個該型別的值,像這樣:
Person{Name: "Robert", Gender: "Male", Age: 33}
可以看到,結構體值的字面量(或簡稱結構體字面量)由其型別的名稱和由花括號包裹的若干鍵值對組成。注意,這裡的鍵值對與字典字面量中的鍵值對的寫法相似,但不相同。這裡的鍵是其型別中的某個欄位的名稱(注意,它不是字串字面量),而對應的值則是欲賦給該欄位的那個值。另外,如果這裡的鍵值對的順序與其型別中的欄位宣告完全相同的話,我們還可以統一省略掉所有欄位的名稱,就像這樣:
Person{"Robert", "Male", 33}
當然,我們在編寫某個結構體型別的值字面量時可以只對它的部分欄位賦值,甚至不對它的任何欄位賦值。這時,未被顯式賦值的欄位的值則為其型別的零值。注意,在上述兩種情況下,欄位的名稱是不能被省略的。
與代表函式值的字面量類似,我們在編寫一個結構體值的字面量時不需要先擬好其型別。這樣的結構體字面量被稱為匿名結構體。與匿名函式類似,我們在編寫匿名結構體的時候需要先寫明其型別特徵(包含若干欄位宣告),再寫出它的值初始化部分。下面,我們依照結構體型別Person
建立一個匿名結構體:
p := struct { Name string Gender string Age uint8 }{"Robert", "Male", 33}
匿名結構體最大的用處就是在內部臨時建立一個結構以封裝資料,而不必正式為其宣告相關規則。而在涉及到對外的場景中,我強烈建議使用正式的結構體型別。
我在本節開始處提到過,結構體型別可以擁有若干方法(注意,匿名結構體是不可能擁有方法的)。所謂方法,其實就是一種特殊的函式。它可以依附於某個自定義型別。方法的特殊在於它的宣告包含了一個接收者宣告。這裡的接收者指代它所依附的那個型別。我們仍以結構體型別Person
為例。下面是依附於它的一個名為Grow
的方法的宣告:
func (person *Person) Grow() { person.Age++ }
如上所示,在關鍵字func
和名稱Grow
之間的那個圓括號及其包含的內容就是接收者宣告。其中的內容由兩部分組成。第一部分是代表它依附的那個型別的值的識別符號。第二部分是它依附的那個型別的名稱。後者表明了依附關係,而前者則使得在該方法中的程式碼可以使用到該型別的值(也稱為當前值)。代表當前值的那個識別符號可被稱為接收者識別符號,或簡稱為接收者。請看下面的示例:
p := Person{"Robert", "Male", 33} p.Grow()
我們可以直接在Person
型別的變數p
之上應用呼叫表示式來呼叫它的方法Grow
。注意,此時方法Grow
的接收者識別符號person
指代的正是變數p
的值。這也是“當前值”這個詞的由來。在Grow
方法中,我們通過使用選擇表示式選擇了當前值的欄位Age
,並使其自增。因此,在語句p.Grow()
被執行之後,p
所代表的那個人就又年長了一歲(p
的Age
欄位的值已變為34
)。
需要注意的是,在Grow
方法的接收者宣告中的那個型別是*Person
,而不是Person
。實際上,前者是後者的指標型別。這也使得person
指代的是p
的指標,而不是它本身。至於為什麼這麼做,我們在講指標的時候在予以揭曉。
說到這裡,熟悉面向物件程式設計的同學可能已經意識到,包含若干欄位和方法的結構體型別就相當於一個把屬性和操作封裝在一起的物件。不過要注意,與物件不同的是,結構體型別(以及任何型別)之間都不可能存在繼承關係。實際上,在Go語言中並沒有繼承的概念。不過,我們可以通過在結構體型別的宣告中新增匿名欄位(或稱嵌入型別)來模仿繼承。具體細節可以參考《Go併發程式設計實戰》中的說明,或者關注我的後續課程。
最後,結構體型別屬於值型別。它的零值並不是nil
,而是其中欄位的值均為相應型別的零值的值。舉個例子,結構體型別Person
的零值若用字面量來表示的話則為