Go開發(fā)工程師
未來3-5年企業(yè)高性能項目不可替代的語言,從基礎(chǔ)到項目實戰(zhàn)再到重構(gòu),真正從入門到精通
Go語言的結(jié)構(gòu)體類型(Struct)比函數(shù)類型更加靈活。它可以封裝屬性和操作。前者即是結(jié)構(gòu)體類型中的字段,而后者則是結(jié)構(gòu)體類型所擁有的方法。
結(jié)構(gòu)體類型的字面量由關(guān)鍵字type、類型名稱、關(guān)鍵字struct,以及由花括號包裹的若干字段聲明組成。其中,每個字段聲明獨占一行并由字段名稱(可選)和字段類型組成。示例如下:
type Person struct {
Name string
Gender string
Age uint8
}
結(jié)構(gòu)體類型Person中有三個字段,分別是Name、Gender和Age。我們可以用字面量創(chuàng)建出一個該類型的值,像這樣:
Person{Name: "Robert", Gender: "Male", Age: 33}
可以看到,結(jié)構(gòu)體值的字面量(或簡稱結(jié)構(gòu)體字面量)由其類型的名稱和由花括號包裹的若干鍵值對組成。注意,這里的鍵值對與字典字面量中的鍵值對的寫法相似,但不相同。這里的鍵是其類型中的某個字段的名稱(注意,它不是字符串字面量),而對應(yīng)的值則是欲賦給該字段的那個值。另外,如果這里的鍵值對的順序與其類型中的字段聲明完全相同的話,我們還可以統(tǒng)一省略掉所有字段的名稱,就像這樣:
Person{"Robert", "Male", 33}
當(dāng)然,我們在編寫某個結(jié)構(gòu)體類型的值字面量時可以只對它的部分字段賦值,甚至不對它的任何字段賦值。這時,未被顯式賦值的字段的值則為其類型的零值。注意,在上述兩種情況下,字段的名稱是不能被省略的。
與代表函數(shù)值的字面量類似,我們在編寫一個結(jié)構(gòu)體值的字面量時不需要先擬好其類型。這樣的結(jié)構(gòu)體字面量被稱為匿名結(jié)構(gòu)體。與匿名函數(shù)類似,我們在編寫匿名結(jié)構(gòu)體的時候需要先寫明其類型特征(包含若干字段聲明),再寫出它的值初始化部分。下面,我們依照結(jié)構(gòu)體類型Person創(chuàng)建一個匿名結(jié)構(gòu)體:
p := struct {
Name string
Gender string
Age uint8
}{"Robert", "Male", 33}
匿名結(jié)構(gòu)體最大的用處就是在內(nèi)部臨時創(chuàng)建一個結(jié)構(gòu)以封裝數(shù)據(jù),而不必正式為其聲明相關(guān)規(guī)則。而在涉及到對外的場景中,我強烈建議使用正式的結(jié)構(gòu)體類型。
我在本節(jié)開始處提到過,結(jié)構(gòu)體類型可以擁有若干方法(注意,匿名結(jié)構(gòu)體是不可能擁有方法的)。所謂方法,其實就是一種特殊的函數(shù)。它可以依附于某個自定義類型。方法的特殊在于它的聲明包含了一個接收者聲明。這里的接收者指代它所依附的那個類型。我們?nèi)砸越Y(jié)構(gòu)體類型Person為例。下面是依附于它的一個名為Grow的方法的聲明:
func (person *Person) Grow() {
person.Age++
}
如上所示,在關(guān)鍵字func和名稱Grow之間的那個圓括號及其包含的內(nèi)容就是接收者聲明。其中的內(nèi)容由兩部分組成。第一部分是代表它依附的那個類型的值的標識符。第二部分是它依附的那個類型的名稱。后者表明了依附關(guān)系,而前者則使得在該方法中的代碼可以使用到該類型的值(也稱為當(dāng)前值)。代表當(dāng)前值的那個標識符可被稱為接收者標識符,或簡稱為接收者。請看下面的示例:
p := Person{"Robert", "Male", 33}
p.Grow()
我們可以直接在Person類型的變量p之上應(yīng)用調(diào)用表達式來調(diào)用它的方法Grow。注意,此時方法Grow的接收者標識符person指代的正是變量p的值。這也是“當(dāng)前值”這個詞的由來。在Grow方法中,我們通過使用選擇表達式選擇了當(dāng)前值的字段Age,并使其自增。因此,在語句p.Grow()被執(zhí)行之后,p所代表的那個人就又年長了一歲(p的Age字段的值已變?yōu)?code class="marker">34)。
需要注意的是,在Grow方法的接收者聲明中的那個類型是*Person,而不是Person。實際上,前者是后者的指針類型。這也使得person指代的是p的指針,而不是它本身。至于為什么這么做,我們在講指針的時候在予以揭曉。
說到這里,熟悉面向?qū)ο缶幊痰耐瑢W(xué)可能已經(jīng)意識到,包含若干字段和方法的結(jié)構(gòu)體類型就相當(dāng)于一個把屬性和操作封裝在一起的對象。不過要注意,與對象不同的是,結(jié)構(gòu)體類型(以及任何類型)之間都不可能存在繼承關(guān)系。實際上,在Go語言中并沒有繼承的概念。不過,我們可以通過在結(jié)構(gòu)體類型的聲明中添加匿名字段(或稱嵌入類型)來模仿繼承。具體細節(jié)可以參考《Go并發(fā)編程實戰(zhàn)》中的說明,或者關(guān)注我的后續(xù)課程。
最后,結(jié)構(gòu)體類型屬于值類型。它的零值并不是nil,而是其中字段的值均為相應(yīng)類型的零值的值。舉個例子,結(jié)構(gòu)體類型Person的零值若用字面量來表示的話則為Person{}。
為源碼文件中聲明的結(jié)構(gòu)體類型Person添加相應(yīng)的字段和方法,使得該文件不會導(dǎo)致任何編譯錯誤并能夠在標準輸出上打印出Robert moved from Beijing to San Francisco.。
需要在Person的聲明中加入一個字段聲明:
Address string
同時為其增加一個名為Move方法,如:
func (person *Person) Move(newAddress string) string {
old := person.Address
person.Address = newAddress
return old
}
注意,后者的實現(xiàn)并不唯一。
請驗證,完成請求
由于請求次數(shù)過多,請先驗證,完成再次請求
打開微信掃碼自動綁定
綁定后可得到
使用 Ctrl+D 可將課程添加到書簽
舉報