結構體
go語言中的結構體,是一種複合型別,有一組屬性構成,這些屬性被稱為欄位。結構體也是值型別,可以使用new來建立。
定義:
type name struct {
field1 type1
field2 type2
...
}
我們可以看到每一個欄位都由一個名字和一個型別構成,不過實際上,如果我們如果不需要使用某個欄位時,可以使用”_”來代替它的名字
並且結構體欄位可以是任意型別,函式,介面,甚至是結構體本身都可以
使用結構體
定義一個Person結構體
type Person struct {
name string
age int
}
使用
// 字面量形式初始化
var p1 = Person{
name: "Tom",
age: 18,
}
var p2 = Person{"Cat", 20}
fmt.Println(p1) //{Tom 18}
fmt.Println(p1.name) // Tom
fmt.Println(p2) // {Cat 20}
p1.age = 20 // 設定p的age欄位的值
fmt.Println(p1) // {Tom 20}
還可以使用new函式來給一個結構體分配記憶體,並返回指向已分配記憶體的指標
var p3 *Person // 宣告一個Person型別的指標
p3 = new(Person) // 分配記憶體
// 上面兩句相當於
p3 := new(Person)
p3.name = "Cat"
p3.age = 10
fmt.Println(p3) // &{Cat 10}
fmt.Println(p3.name) //
我們可以直接使用結構體指標通過”.”來訪問結構體的欄位,就像直接使用結構體例項一樣, go會自動進行轉換
還有一種叫做混合字面量的語法來宣告,如下,這其實只是一種簡寫方式,底層還是呼叫new方法
var p4 = &Person{"Dog", 10} // 同樣返回的是Person型別的指標
fmt.Println(p4) // &{Dog 10}
fmt.Println(p4.name) // Dog
匿名欄位
go語言的結構體還支援匿名欄位,也就是說一個只有型別而沒有欄位名(連”_”都沒有)的欄位,被匿名嵌入的也可以是任何型別,此時型別名就是欄位的名字,也就是我們可以直接使用型別名為欄位名來訪問匿名欄位.
另外如果匿名欄位是另一個結構體,這就叫做內嵌結構體,這個特性可以模擬類似繼承的行為。
type Person struct {
name string
age int
}
type Student struct {
Person
int
}
// 定義一個Student型別的變數
var s = Student{Person{"gdb", 10}, 10}
// 可以使用如下的方法訪問內部結構體中的欄位
fmt.Println(s.Person.name)
// 也可以這樣訪問,go將自動使用Person的name屬性,不過如果在Student中也定義了name欄位,這裡就不能使用了
fmt.Println(s.name)
// 訪問int型別的匿名欄位,此時型別就是欄位的名字
fmt.Println(s.int)
注意:這樣如果兩個欄位有相同的名字時,外部的名字會覆蓋內部的;如果同一級別出現相同名字的欄位,會出錯,需要注意;並且不能同時嵌⼊某⼀型別和其指標型別,因為它們名字相同。
標籤
結構體中的欄位除了可以有名稱和型別以外,還可以有標籤。它是一個附屬於欄位的字串,可以是文件或其他的重要標記。後面說反射時再說。
方法
之前學習的面嚮物件語言,比如說Java, Python中,有類的概念,每個類都可以有自己的成員變數,成員方法,它們都是定義在類中的
go語言中的結構體就類似與面嚮物件語言的類,而結構體的欄位就相當於類中的成員變數,結構體也可以有方法,但是不是直接定義在結構體中的,go語言中有一個接收者的概念,我們可以將函式作用在一個接收者,此時這個函式就被稱為方法
接收者是某種型別的變數,不僅僅可以是結構體,幾乎任何型別都可以是結構體,比如: int,bool, string或陣列的別名型別,甚至可以是函式型別,不過不能是介面型別
定義方法的示例:
type Person struct {
name string
age int
}
// 使用Person型別的例項做接收者,這就是一個Person型別方法,方法名前面括號中的就是接收者
func (this Person) getName() string {
return this.name
}
// Peron型別的指標物件也可以作為接收者
func (this *Person) setName(name string) {
this.name = name
}
tom := Person{"Tom", 20}
fmt.Println(tom) // {Tom 20}
fmt.Println(tom.getName()) // Tom
tom.changeName("Bob")
fmt.Println(tom) // {Bob 20}
這裡有一點需要注意:型別和繫結它的方法必須在同一個包中(不一定要在同一個檔案中)
這裡使用型別直接作為接收著 和 型別的指標作為接收者的區別,就相當於普通函式中,值型別的引數和引用型別引數的區別;即在方法中對型別的例項的操作,不會影響外部的例項的值,而使用型別指標的例項作為引用引數,在方法內部修改會影響外部的例項
匿名欄位
我們也可以使用結構體內部的匿名欄位,作為方法的接收者,此時這個結構體,仍然可以呼叫這個方法,此時編譯器會負責查詢
type Person struct {
name string
age int
}
type Student struct {
Person
score int
}
func (p *Person) show() {
fmt.Println("My name is " + p.name + ", I'm " + strconv.Itoa(p.age) + " years old")
}
tom := Person{"Tom", 20}
// 呼叫匿名欄位作為接收器的方法
tom.show() // My name is Tom, I'm 20 years old
在此基礎上,我們還可以在結構體上,實現與匿名欄位同名的方法,就像物件導向中的重寫類似,編譯器會先查詢結構體例項作為接收器的方法。
方法集
根據定義結構體以及方法的不同,方法集也有所不同,瞭解他們,對理解介面有幫助
type T struct {
name string
age int
}
type G struct {
T
action string
}
type S struct {
*T
sel string
}
- 型別 T 的方法集包含所有接收者為 T 的方法
- 型別T的方法集包含所有接收者為 T的方法(因為go會自動解引用)和所有接收者為 T 的方法
- 型別G包含匿名欄位 T, 則G的方法集,僅僅包含T型別的方法集
- 型別S包含匿名欄位 *T,則S的方法集,包含T和*T型別的方法集