引用曾經看到的一篇文章裏面對 Golang 中結構體的描述,若是說 Golang 的基礎類型是原子,那麼數組
結構體就是分子。咱們都知道分子是由原子組成的,換言之就是結構體裏面能夠包含基礎類型、切片、app
字典、數組以及結構體自身。ide
結構體類型的定義函數
結構體定義的通常方式ui
type identifier struct { field1 type1 field2 type2 ... }
結構體裏的字段都有名字,而對於字段在代碼中歷來也不被使用到,那麼能夠命名它爲_。對於相同類型的指針
字段咱們可使用簡寫的形式,好比:對象
type T struct { a,b int }
還有一個須要注意的地方是結構體中字段的大小寫,首字母大寫表示公有變量,首字母小寫表示私有變量,blog
至關於其餘語言類成員變量的Public、Private ,可是這個公有變量和私有變量在 Go 中是相對於 Package 來繼承
說的,在同一個 Package 都是公有的。string
結構體變量的建立
1 最多見的建立結構體變量形式
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c Circle = Circle{ x:100, y:100, Radius:50, } fmt.Println(c) } -------------------------------- {100 100 50}
經過顯示指定結構體內部字段的名稱和初始值初始化結構體,能夠只指定部分字段的初始值,或一個都不指定,
那些沒有被指定初始值的字段會自動初始化爲相應類型的零值。
2 結構體變量建立的第二種形式是不顯示指定字段而是根據字段順序初始化,可是這樣須要顯示的提供全部字段的
初始值
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c Circle = Circle{100,100,50} fmt.Println(c) } ------------------------------- {100 100 50}
3 使用 new() 函數建立結構體變量
結構體變量和普通變量都有指針形式,使用取地址符就能夠獲得結構體的指針類型
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c *Circle = &Circle{100,100,50} fmt.Printf("%+v\n", c) } ------------------------------------------ &{x:100 y:100 Radius:50}
下面看一下使用 new() 建立一個零值結構體,全部的字段都會被初始化成對應類型的零值
package main import "fmt" type Circle struct { x int y int Radius int } func main() { var c *Circle = new(Circle) fmt.Printf("%+v\n", c) } ----------------------------------- &{x:0 y:0 Radius:0}
注意:new 函數返回的是指針類型
結構體的參數傳遞
看一個實例
package main import "fmt" // 聲明一個結構體 type employee struct { name,address string height,weight float64 } // 定一個方法,該方法的參數是一個結構體,主要用於修改結構體成員中name的值 func modifyAttribute(emy employee) { emy.name = "newer" fmt.Println(emy) } func main() { // 初始化結構體 emy := employee{ name:"xiaoming", address:"beijing", height:172.0, weight:75.3, } // 打印修改前的值 fmt.Println(emy) // 調用modifyAttribute modifyAttribute(emy) // 打印修改後值 fmt.Println(emy) } ---------------------------------- 輸出結果 {xiaoming beijing 172 75.3} {newer beijing 172 75.3} {xiaoming beijing 172 75.3}
從上面的輸出結果上來看,雖然在 modifyAttribute 方法中修改了 name 值,可是在 main 函數中打印 name 的
值並無變化,說明這是一個值傳遞
咱們把 modifyAttribute 函數的參數變成結構體的指針類型,以下
func modifyAttribute(emy *employee) { emy.name = "newer" fmt.Println(emy) } func main() { // 初始化結構體 emy := employee{ name:"xiaoming", address:"beijing", height:172.0, weight:75.3, } // 打印修改前的值 fmt.Println(emy) // 調用modifyAttribute modifyAttribute(&emy) // 打印修改後值 fmt.Println(emy) } ---------------------------- 輸出結果 {xiaoming beijing 172 75.3} &{newer beijing 172 75.3} {newer beijing 172 75.3}
咱們看到在函數 modifyAttribute 中的修改影響到了 main 函數中的 name 值,這裏是引用傳遞
再看一個例子:編寫擴大圓半徑的函數
package main import "fmt" // 定義一個結構體 Circle type Circle struct { x int y int Radius int } // 經過值傳遞擴大圓半徑 func expandByValue(c Circle) { c.Radius *= 2 } // 經過引用傳遞擴大圓半徑 func expandByPointer(c *Circle) { c.Radius *= 2 } func main() { c := Circle{ Radius:50, } expandByValue(c) fmt.Println(c) expandByPointer(&c) fmt.Println(c) } -------------------------------- 輸出結果 {0 0 50} {0 0 100}
咱們能夠從上面的輸出中再次看到經過值傳遞,在函數裏面修改結構體的狀態不會影響原有結構
體的狀態,經過值傳遞就不同。
結構體方法
Go 語言不是面向對象的語言,在 Go 語言中沒有類的概念,結構體正是類的替代品。類能夠附加不少成員方法,
結構體也能夠。看一個實例,如何給結構體綁定方法:
package main import ( "fmt" "math" ) // 定義一個結構體 Circle type Circle struct { x int y int Radius int } // 計算圓的面積 第一個括號裏面表示的是方法的接收者 這裏方法的接收者是結構體 Circle // Area() 表示函數名 float64 表示函數的返回值類型 func (c Circle) Area() float64{ return math.Pi * float64(c.Radius) * float64(c.Radius) } // 計算圓的周長 func (c Circle) Circumference() float64 { return 2 *math.Pi * float64(c.Radius) } func main() { // 初始化結構體 c := Circle{ Radius:50, } fmt.Println(c.Area(),c.Circumference()) } ------------------------------------------------- 輸出結果 7853.981633974483 314.1592653589793
結構體的指針方法
若是使用結構體方法的形式給 Circle 增長一個擴大圓半徑的方法,會發現半徑仍是擴大不了
func (c Circle) expand() { c.Radius *= 2 }
這個方法和前面的 expandByValue 函數是等價的,只是調整了一下第一個參數的位置,在參數傳遞的時候依然是值傳遞
,因此,仍是沒法起到擴大圓半徑的做用,這個時候就須要使用結構體的指針方法
func (c *Circle) expand() { c.Radius *= 2 }
結構體指針方法和值方法在調用上沒有區別,只是一個能夠改變結構體內部狀態,另外一個不會。另外指針方法可使用結
構體變量調用,值方法也可使用結構體指針變量使用
結構體變量調用指針方法(好比調用計算圓周長的方法):
package main import ( "fmt" "math" ) // 定義一個結構體 Circle type Circle struct { x int y int Radius int } // 計算圓的周長 func (c *Circle) Circumference() float64 { c.Radius *= 2 return 2 *math.Pi * float64(c.Radius) } func main() { // 初始化結構體 c := Circle{ Radius:50, } fmt.Println(c.Circumference()) } ----------------------------------------- 輸出結果 628.3185307179587
使用結構體指針變量調用值方法:
package main import ( "fmt" "math" ) // 定義一個結構體 Circle type Circle struct { x int y int Radius int } // 計算圓的面積 func (c Circle) Area() float64{ return math.Pi * float64(c.Radius) * float64(c.Radius) } func main() { // 初始化結構體 c := &Circle{ Radius:50, } fmt.Println(c.Area()) } --------------------------------- 輸出結果 7853.981633974483
內嵌結構體
結構體做爲一種變量能夠嵌套在另外一個結構體中做爲一個字段使用,這種內嵌結構體在 Go 語言中稱之爲
組合
package main import "fmt" // 定義一個結構體 type Pointer struct { x int y int } func (p Pointer) show() { fmt.Println(p.x,p.y) } // 定義另外一個結構體 type Circle struct { // 將結構體 Pointer 嵌套在 Circle loc Pointer Radius int } func main() { c := Circle{ loc:Pointer{ x:100, y:100, }, Radius:50, } fmt.Println(c) fmt.Println(c.loc) fmt.Println(c.loc.x,c.loc.y) c.loc.show() } ----------------------------------- 輸出結果 {{100 100} 50} {100 100} 100 100 100 100
匿名內嵌結構體
還有一種特殊的內嵌結構體形式,內嵌的結構體不提供名稱。這時外面的結構體直接繼承內嵌結構體的全部
內部字段和方法
package main import "fmt" // 定義一個結構體 type Pointer struct { x int y int } func (p Pointer) show() { fmt.Println(p.x,p.y) } // 定義另外一個結構體 type Circle struct { // 匿名內嵌結構體 Pointer Radius int } func main() { c := Circle{ Pointer:Pointer{ x:100, y:100, }, Radius:50, } fmt.Println(c) fmt.Println(c.Pointer) fmt.Println(c.x,c.y) //繼承字段 fmt.Println(c.Pointer.x,c.Pointer.y) c.show() // 繼承方法 c.Pointer.show() } -------------------------------------- 輸出結果 {{100 100} 50} {100 100} 100 100 100 100 100 100 100 100
Go 語言的結構體沒有多態性
咱們知道面嚮對象語言的三大特性,封裝、繼承、多態。其中多態是指父類定義的方法能夠調用子類實現的方法,不一樣的子類有不一樣的實現方法,
從而給父類的方法帶來了多樣的不一樣行爲。而 Go 不是面嚮對象語言,它的結構體也不支持結構體,以下示例
package main import "fmt" // 定義一個fruit結構體 type Fruit struct { } func (f Fruit) eat() { fmt.Println("eat Fruit") } func (f Fruit) enjoy() { fmt.Println("smell first") f.eat() fmt.Println("clean finally") } // 定一個apple結構體 type Apple struct { Fruit } func (a Apple) eat() { fmt.Println("eat apple") } // 定義一個banana結構體 type Banana struct { Fruit } func (b Banana) eat() { fmt.Println("eat banana") } func main() { apple := Apple{} banana := Banana{} apple.enjoy() banana.enjoy() } ----------------------------- 輸出結果 smell first eat Fruit clean finally smell first eat Fruit clean finally
從上面的輸出結果中能夠看到,雖然外部結構體能夠繼承內部結構體的方法和字段,可是外部結構體的方法不能覆蓋內部結構體的方法,
enjoy 方法調用的 eat 方法仍是 Fruit 本身的 eat 方法,它並無被外面結構體的方法覆蓋掉