Go語言不是一門面向對象的語言,沒有對象和繼承,也沒有面向對象的多態、重寫相關特性。數據結構
Go所擁有的是數據結構,它能夠關聯方法。Go也支持簡單但高效的組合(Composition),請搜索面向對象和組合。函數
雖然Go不支持面向對象,但Go經過定義數據結構的方式,也能實現與Class類似的功能。spa
一個簡單的例子,定義一個Animal數據結構:3d
type Animal struct { name string speak string }
這就像是定義了一個class,有本身的屬性。指針
在稍後,將會介紹如何向這個數據結構中添加方法,就像爲類定義方法同樣。不過如今,先簡單介紹下數據結構。code
除了int、string等內置的數據類型,咱們能夠定義structure來自定義數據類型。對象
建立數據結構最簡單的方式:blog
bm_horse := Animal{ name:"baima", speak:"neigh", }
注意,上面最後一個逗號","不能省略,Go會報錯,這個逗號有助於咱們去擴展這個結構,因此習慣後,這是一個很好的特性。繼承
上面bm_horse := Animal{}
中,Animal就像是一個類,這個聲明和賦值的操做就像建立了一個Animal類的實例,也就是對象,其中對象名爲bm_horse
,它是這個實例的惟一標識符。這個對象具備屬性name和speak,它們是每一個對象所擁有的key,且它們都有本身的值。從面向對象的角度上考慮,這其實很容易理解。內存
還能夠根據Animal數據結構再建立另一個實例:
hm_horse := Animal{ name:"heima", speak:"neigh", }
bm_horse
和hm_horse
都是Animal的實例,根據Animal數據結構建立而來,這兩個實例都擁有本身的數據結構。以下圖:
從另外一種角度上看,bm_horse
這個名稱實際上是這個數據結構的一個引用。再進一步考慮,其實面向對象的類和對象也是一種數據結構,每個對象的名稱(即bm_horse
)都是對這種數據結構的引用。關於這一點,在後面介紹指針的時候將很是有助於理解。
如下是兩外兩種有效的數據結構定義方式:
// 定義空數據結構 bm_horse := Animal{} // 或者,先定義一部分,再賦值 bm_horse := Animal {name:"baima"} bm_horse.speak = "neigh"
此外,還能夠省略數據結構中的key部分(也就是屬性的名稱)直接爲數據結構中的屬性賦值,只不過這時賦的值必須和key的順序對應。
bm_horse := Animal{"baima","neigh"}
在數據結構的屬性數量較少的時候,這種賦值方式也是不錯的,但屬性數量多了,不建議如此賦值,由於很容易混亂。
要訪問一個數據結構中的屬性,以下:
package main import ("fmt") func main(){ type Animal struct { name string speak string } bm_horse := Animal{"baima","neigh"} fmt.Println("name:",bm_horse.name) fmt.Println("speak:",bm_horse.speak) }
前面說過,Animal是一個數據結構的模板(就像類同樣),不是實例,bm_horse
纔是具體的實例,有本身的數據結構,因此,要訪問本身數據結構中的數據,能夠經過本身的名稱來訪問本身的屬性:
bm_horse.name bm_horse.speak
bm_horse := Animal{}
表示返回一個數據結構給bm_horse,bm_horse指向這個數據結構,也能夠說bm_horse是這個數據結構的引用。
除此,還有另外一種賦值方式,比較下兩種賦值方式:
bm_horse := Animal{"baima","neigh"} ref_bm_horse := &Animal{"baima","neigh"}
這兩種賦值方式,有何不一樣?
:=
操做符都聲明左邊的變量,並賦值變量。賦值的內容基本神似:
bm_horse
,bm_horse
今後變成Animal的實例;&
在數據結構前面,它表示返回這個數據結構的引用,也就是這個數據結構的地址,因此ref_bm_horse
也指向這個數據結構。那bm_horse
和ref_bm_horse
都指向這個數據結構,有什麼區別?
實際上,賦值給bm_horse
的是Animal實例的地址,賦值給ref_bm_horse
是一箇中間的指針,這個指針裏保存了Animal實例的地址。它們的關係至關於:
bm_horse -> Animal{} ref_bm_horse -> Pointer -> Animal{}
其中Pointer在內存中佔用一個長度爲一個機器字長的單獨數據塊,64位機器上一個機器字長是8字節,因此賦值給ref_bm_horse
的這個8字節長度的指針地址,這個指針地址再指向Animal{}
,而bm_horse
則是直接指向Animal{}
。
若是還不明白,我打算用perl語言的語法來解釋它們的區別,由於C和Go的指針太過"晦澀"。
在Perl中,一個hash結構使用%
符號來表示,例如:
%Animal = ( name => "baima", speak => "neigh", );
這裏的"Animal"表示的是這個hash結構的名稱,而後經過%+NAME
的方式來引用這個hash數據結構。其實hash結構的名稱"Animal"就是這個hash結構的一個引用,表示指向這個hash結構,只不過這個Animal
是建立hash結構是就指定好的已命名的引用。
perl中還支持顯式地建立一個引用。例如:
$ref_myhash = \%Animal;
%Animal
表示的是hash數據結構,加上\
表示這個數據結構的一個引用,這個引用指向這個hash數據結構。perl中的引用是一個變量,因此使用$ref_myhash
表示。
也就是說,hash結構的名稱Animal
和$ref_myhash
是徹底等價的,都是hash結構的引用,也就是指向這個數據結構,也就是指針。因此,%Animal
能表示取hash結構的屬性,%$ref_myhash
也能表示取hash結構的屬性,這種從引用取回hash數據結構的方式稱爲"解除引用"。
另外,$ref_myhash
是一個變量類型,而%Animal
是一個hash類型。
引用變量能夠賦值給另外一個引用變量,這樣兩個引用都將指向同一個數據結構:
$ref_myhash1 = $ref_myhash;
如今,$ref_myhash
、$ref_myhash1
和Animal
都指向同一個數據結構。
總結下上面perl相關的代碼:
%Animal = ( name => "baima", speak => "neigh", ); $ref_myhash = \%Animal; $ref_myhash1 = $ref_myhash;
%Animal
是hash結構,Animal
、$ref_myhash
、$ref_myhash1
都是這個hash結構的引用。
回到Go語言的數據結構:
bm_horse := Animal{} hm_horse := &Animal{}
這裏的Animal{}
是一個數據結構,至關於perl中的hash數據結構:
( name => "baima", speak => "neigh", )
bm_horse是數據結構的直接賦值對象,它直接表示數據結構,因此它等價於前面perl中的%Animal
。而hm_horse
是Animal{}
數據結構的引用,它等價於perl中的Animal
、$ref_myhash
、$ref_myhash1
。
之因此Go中的指針很差理解,就是由於數據結構bm_horse和引用hm_horse都沒有任何額外的標註,看上去都像是一種變量。但其實它們是兩種不一樣的數據類型:一種是數據結構,一種是引用。
星號有兩種用法:
x *int
表示變量x是一個引用,這個引用指向的目標數據是int類型。更通用的形式是x *TYPE
*x
表示x是一個引用,*x
表示解除這個引用,取回x所指向的數據結構,也就是說這是 一個數據結構,只不過這個數據結構多是內置數據類型,也多是自定義的數據結構x *int
的x是一個指向int類型的引用,而&y
返回的也是一個引用,因此&y
的y若是是int類型的數據,&y
能夠賦值給x *int
的x。
注意,x的數據類型是*int
,不是int,雖然x所指向的是數據類型是int。就像前面perl中的引用只是一個變量,而其指向的倒是一個hash數據結構同樣。
*x
表明的是數據結構自身,因此若是爲其賦值(如*x = 2
),則新賦的值將直接保存到x指向的數據中。
例如:
package main import ("fmt") func main(){ var a *int c := 2 a = &c d := *a fmt.Println(*a) // 輸出2 fmt.Println(d) // 輸出2 }
var a *int
定義了一個指向int類型的數據結構的引用。a = &c
中,由於&c
返回的是一個引用,指向的是數據結構c,c是int類型的數據結構,將其賦值給a,因此a也指向c這個數據結構,也就是說*a
的值將等於2。因此d := *a
賦值後,d自身是一個int類型的數據結構,其值爲2。
package main import "fmt" func main() { var i int = 10 println("i addr: ", &i) // 數據對象10的地址:0xc042064058 var ptr *int = &i fmt.Printf("ptr=%v\n", ptr) // 0xc042064058 fmt.Printf("ptr addr: %v\n", &ptr) // 指針對象ptr的地址:0xc042084018 fmt.Printf("ptr地址: %v\n", *&ptr) // 指針對象ptr的值0xc042064058 fmt.Printf("ptr->value: %v", *ptr) // 10 }
Go函數給參數傳遞值的時候是以複製的方式進行的。
由於複製傳值的方式,若是函數的參數是一個數據結構,將直接複製整個數據結構的副本傳遞給函數,這有兩個問題:
例如,第一個問題:
package main import ("fmt") type Animal struct { name string weight int } func main(){ bm_horse := Animal{ name: "baima", weight: 60, } add(bm_horse) fmt.Println(bm_horse.weight) } func add(a Animal){ a.weight += 10 }
上面的輸出結果仍然爲60。add函數用於修改Animal的實例數據結構中的weight屬性。當執行add(bm_horse)
的時候,bm_horse
傳遞給add()函數,但並非直接傳遞給add()函數,而是複製一份bm_horse
的副本賦值給add函數的參數a,因此add()中修改的a.weight
的屬性是bm_horse
的副本,而不是直接修改的bm_horse,因此上面的輸出結果仍然爲60。
爲了修改bm_horse所在的數據結構的值,須要使用引用(指針)的方式傳值。
只需修改兩個地方便可:
package main import ("fmt") type Animal struct { name string weight int } func main(){ bm_horse := &Animal{ name: "baima", weight: 60, } add(bm_horse) fmt.Println(bm_horse.weight) } func add(a *Animal){ a.weight += 10 }
爲了修改傳遞給函數參數的數據結構,這個參數必須是直接指向這個數據結構的。因此使用add(a *Animal)
,既然a是一個Animal數據結構的一個實例的引用,因此調用add()的時候,傳遞給add()中的參數必須是一個Animal數據結構的引用,因此bm_horse
的定義語句中使用&
符號。
當調用到add(bm_horse)
的時候,由於bm_horse
是一個引用,因此賦值給函數參數a時,複製的是這個數據結構的引用,使得add能直接修改其外部的數據結構屬性。
大多數時候,傳遞給函數的數據結構都是它們的引用,但極少數時候也有需求直接傳遞數據結構。
能夠爲數據結構定義屬於本身的函數。
package main import ("fmt") type Animal struct { name string weight int } func (a *Animal) add() { a.weight += 10 } func main() { bm_horse := &Animal{"baima",70} bm_horse.add() fmt.Println(bm_horse.weight) // 輸出80 }
上面的add()函數定義方式func (a *Animal) add(){}
,它所表示的就是定義於數據結構Animal上的函數,就像類的實例方法同樣,只要是屬於這個數據結構的實例,都能直接調用這個函數,正如bm_horse.add()
同樣。
面向對象中有構造器(也稱爲構造方法),能夠根據類構造出類的實例:對象。
Go雖然不支持面向對象,沒有構造器的概念,但也具備構造器的功能,畢竟構造器只是一個方法而已。只要一個函數可以根據數據結構返回這個數據結構的一個實例對象,就能夠稱之爲"構造器"。
例如,如下是Animal數據結構的一個構造函數:
func newAnimal(n string,w int) *Animal { return &Animal{ name: n, weight: w, } }
如下返回的是非引用類型的數據結構:
func newAnimal(n string,w int) Animal { return Animal{ name: n, weigth: w, } }
通常上面的方法類型稱爲工廠方法,就像工廠同樣根據模板不斷生成產品。但對於建立數據結構的實例來講,通常仍是會採用內置的new()方式。
儘管Go沒有構造器,但Go還有一個內置的new()函數用於爲一個數據結構分配內存。其中new(x)
等價於&x{}
,如下兩語句等價:
bm_horse := new(Animal) bm_horse := &Animal{}
使用哪一種方式取決於本身。但若是要進行初始化賦值,通常採用第二種方法,可讀性更強:
# 第一種方式 bm_horse := new(Animal) bm_horse.name = "baima" bm_horse.weight = 60 # 第二種方式 bm_horse := &Animal{ name: "baima", weight: 60, }
在前面出現的數據結構中的字段數據類型都是簡簡單單的內置類型:string、int。但數據結構中的字段能夠更復雜,例如能夠是map、array等,還能夠是自定義的數據類型(數據結構)。
例如,將一個指向同類型數據結構的字段添加到數據結構中:
type Animal struct { name string weight int father *Animal }
其中在此處的*Animal
所表示的數據結構實例極可能是其它的Animal實例對象。
上面定義了father,還能夠定義son,sister等等。
例如:
bm_horse := &Animal{ name: "baima", weight: 60, father: &Animal{ name: "hongma", weight: 80, father: nil, }, }
Go語言支持Composition(組合),它表示的是在一個數據結構中嵌套另外一個數據結構的行爲。
package main import ( "fmt" ) type Animal struct { name string weight int } type Horse struct { *Animal // 注意此行 speak string } func (a *Animal) hello() { fmt.Println(a.name) fmt.Println(a.weight) //fmt.Println(a.speak) } func main() { bm_horse := &Horse{ Animal: &Animal{ // 注意此行 name: "baima", weight: 60, }, speak: "neigh", } bm_horse.hello() }
上面的Horse數據結構中包含了一行*Animal
,表示Animal的數據結構插入到Horse的結構中,這就像是一種面向對象的類繼承。注意,沒有給該字段顯式命名,但能夠隱式地訪問Horse組合結構中的字段和函數。
另外,在構建Horse實例的時候,必須顯式爲其指定字段名(儘管數據結構中並無指定其名稱),且字段的名稱必須和數據結構的名稱徹底相同。
而後調用屬於Animal數據結構的hello方法,它只能訪問Animal中的屬性,因此沒法訪問speak屬性。
不少人認爲這種代碼共享的方式比面向對象的繼承更加健壯。
例如,將上面屬於Animal數據結構的hello函數重載爲屬於Horse數據結構的hello函數:
package main import ( "fmt" ) type Animal struct { name string weight int } type Horse struct { *Animal // 注意此行 speak string } func (h *Horse) hello() { fmt.Println(h.name) fmt.Println(h.weight) fmt.Println(h.speak) } func main() { bm_horse := &Horse{ Animal: &Animal{ // 注意此行 name: "baima", weight: 60, }, speak: "neigh", } bm_horse.hello() }