結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每一個值稱爲結構體的成員。git
用結構體的經典案例:學校的學生信息,每一個學生信息包含一個惟一的學生學號、學生的名字、學生的性別、家庭住址等等。全部的這些信息都須要綁定到一個實體中,能夠做爲一個總體單元被複制,做爲函數的參數或返回值,或者是被存儲到數組中,等等。數組
結構體也是值類型,所以能夠經過 new 函數來建立。數據結構
組成結構體類型的那些數據稱爲字段(fields)。字段有如下特性:app
關於 Go 語言的類(class)函數
Go 語言中沒有「類」的概念,也不支持「類」的繼承等面向對象的概念。Go 語言的結構體與「類」都是複合結構體,但 Go 語言中結構體的內嵌配合接口比面向對象具備更高的擴展性和靈活性。佈局
Go 語言不只認爲結構體能擁有方法,且每種自定義類型也能夠擁有本身的方法。網站
使用關鍵字 type 能夠將各類基本類型定義爲自定義類型,基本類型包括整型、字符串、布爾等。結構體是一種複合的基本類型,經過 type 定義爲自定義類型後,使結構體更便於使用。指針
結構體的定義格式以下:code
type 結構體類型名 struct { 字段1 字段1類型 字段2 字段2類型 … }
其中:對象
一、結構體類型名:標識自定義結構體的名稱,在同一個包內不能重複。
二、字段1:表示結構體字段名。結構體中的字段名必須惟一。
三、字段1類型:表示結構體字段的具體類型。
舉個例子,咱們定義一個 Student(學生)結構體,代碼以下:
type Student struct{ id int name string age int gender int // 0 表示女生,1 表示男生 addr string }
在這裏,Student 的地位等價於 int、byte、bool、string 等類型
一般一行對應一個結構體成員,成員的名字在前,類型在後
不過若是相鄰的成員類型若是相同的話能夠被合併到一行:
type Student struct{ id int name string age, gender int addr string }
這樣咱們就擁有了一個 Student 的自定義類型,它有 id、name、age等字段。
這樣咱們使用這個 Student 結構體就可以很方便的在程序中表示和存儲學生信息了。
結構體類型能夠經過引用自身來定義。這在定義鏈表或二叉樹的元素(一般叫節點)時特別有用,此時節點包含指向臨近節點的連接(地址)。
以下所示,鏈表中的 su
,樹中的 ri
和 le
分別是指向別的節點的指針。
type Node struct { data float64 su *Node }
type Node struct { pr *Node data float64 su *Node }
type Tree struct { le *Tree data float64 ri *Tree }
結構體的定義只是一種內存佈局的描述,只有當結構體實例化時,纔會真正地分配內存,所以必須在定義結構體並實例化後才能使用結構體的字段。
實例化就是根據結構體定義的格式建立一份與格式一致的內存區域,結構體實例與實例間的內存是徹底獨立的。
Go語言能夠經過多種方式實例化結構體,根據實際須要能夠選用不一樣的寫法。
結構體自己也是一種類型,咱們能夠像聲明內置類型同樣使用 var 關鍵字聲明結構體類型。
基本實例化格式以下:
var 結構體實例 結構體類型
對 Student 進行實例化,代碼以下:
type Student struct{ id int name string age int gender int // 0 表示女生,1 表示男生 addr string } func main() { var stu1 Student stu1.id = 120100 stu1.name = "Conan" stu1.age = 18 stu1.gender = 1 fmt.Println("stu1 = ", stu1) // stu1 = {120100 Conan 18 1 } }
注意:沒有賦值的字段默認爲該字段類型的零值,此時 addr = ""
咱們能夠經過 點 "."
的方式來訪問結構體的成員變量,如 stu1.name
,結構體成員變量的賦值方法與普通變量一致。
Go 語言中,還可使用 new 關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化後會造成指針類型的結構體。
使用 new 的格式以下:
變量名 := new(類型)
Go 語言讓咱們能夠像訪問普通結構體同樣使用 點"."
來訪問結構體指針的成員,例如:
type Student struct{ id int name string age int gender int // 0 表示女生,1 表示男生 addr string } func main() { stu2 := new(Student) stu2.id = 120101 stu2.name = "Kidd" stu2.age = 23 stu2.gender = 1 fmt.Println("stu2 = ", stu2) // stu2 = &{120101 Kidd 23 1 } }
通過 new 實例化的結構體實例在成員賦值上與基本實例化的寫法一致。
注意:在 Go 語言中,訪問結構體指針的成員變量時能夠繼續使用 點"."
,這是由於 Go 語言爲了方便開發者訪問結構體指針的成員變量,使用了語法糖(Syntactic sugar)技術,將 stu2.name 形式轉換爲 (*stu2).name。
在 Go 語言中,對結構體進行 &
取地址操做時,視爲對該類型進行一次 new 的實例化操做,取地址格式以下:
變量名 := &結構體類型{}
取地址實例化是最普遍的一種結構體實例化方式,具體代碼以下:
type Student struct{ id int name string age int gender int // 0 表示女生,1 表示男生 addr string } func main() { stu3 := &Student{} stu3.id = 120102 stu3.name = "Lan" stu3.age = 18 stu3.gender = 0 fmt.Println("stu3 = ", stu3) // stu3 = &{120102 Lan 18 0 } }
結構體在實例化時能夠直接對成員變量進行初始化,初始化有兩種形式分別是以字段「鍵值對」形式和多個值的列表形式。
鍵值對形式的初始化適合選擇性填充字段較多的結構體,多個值的列表形式適合填充字段較少的結構體。
特別地,還有一種初始化匿名結構體。
結構體可使用「鍵值對」(Key value pair)初始化字段,每一個「鍵」(Key)對應結構體中的一個字段,鍵的「值」(Value)對應字段須要初始化的值。
鍵值對的填充是可選的,不須要初始化的字段能夠不填入初始化列表中。
結構體實例化後字段的默認值是字段類型的零值,例如 ,數值爲 0、字符串爲 ""(空字符串)、布爾爲 false、指針爲 nil 等。
鍵值對初始化的格式以下:
變量名 := 結構體類型名{ 字段1: 字段1的值, 字段2: 字段2的值, ... }
注意:
一、字段名只能出現一次。
二、鍵值之間以 : 分隔,鍵值對之間以 , 分隔。
使用鍵值對形式初始化結構體的代碼以下:
stu4 := Student{ id: 120103, name: "Gin", age: 25, gender: 1, addr: "unknown", } fmt.Println("stu4 = ", stu4) // stu4 = {120103 Gin 25 1 unknown}
Go語言能夠在「鍵值對」初始化的基礎上忽略「鍵」,也就是說,可使用多個值的列表初始化結構體的字段。
多個值使用逗號分隔初始化結構體,例如:
變量名 := 結構體類型名{ 字段1的值, 字段2的值, ... }
注意:
一、必須初始化結構體的全部字段。
二、每個初始值的填充順序必須與字段在結構體中的聲明順序一致。
三、鍵值對與值列表的初始化形式不能混用。
使用多個值列表初始化結構體的代碼以下:
stu5 := Student{ 120104, "Kogorou", 38, 1, "毛利偵探事務所", } fmt.Println("stu5 = ", stu5) // stu5 = {120104 Kogorou 38 1 毛利偵探事務所}
匿名結構體沒有類型名稱,無須經過 type 關鍵字定義就能夠直接使用。
例如:
package main import ( "fmt" ) func main() { var user struct{name string; age int} user.name = "Conan" user.age = 18 fmt.Println("user = ", user) // user = {Conan 18} }
當使用 = 對結構體賦值時,更改其中一個結構體的值不會影響另外的值:
package main import ( "fmt" ) type Student struct { id int name string age int gender int // 0 表示女生,1 表示男生 addr string } func main() { var stu1 Student stu1.id = 120100 stu1.name = "Conan" stu1.age = 18 stu1.gender = 1 stu6 := stu1 fmt.Println("stu1 = ", stu1) // stu1 = {120100 Conan 18 1 } fmt.Println("stu6 = ", stu6) // stu6 = {120100 Conan 18 1 } stu6.name = "柯南" fmt.Println("stu1 = ", stu1) // stu1 = {120100 Conan 18 1 } fmt.Println("stu6 = ", stu6) // stu6 = {120100 柯南 18 1 } }
若是結構體的所有成員都是能夠比較的,那麼結構體也是能夠比較的;若是結構體中存在不可比較的成員變量,好比說切片、map等,那麼結構體就不能比較。這時若是強行用 ==、!= 來進行判斷的話,程序會直接報錯,咱們能夠用 DeepEqual 來進行深度比較。
若是結構體的所有成員都是能夠比較的,那麼兩個結構體將可使用 == 或 != 運算符進行比較。
相等比較運算符 == 將比較兩個結構體的每一個成員,所以下面兩個比較的表達式是等價的:
type Student struct { id int name string } func main() { var stu1 Student stu1.id = 120100 stu1.name = "Conan" stu6 := stu1 stu6.name = "柯南" fmt.Println(stu1.id == stu6.id && stu1.name == stu6.name) // "false" fmt.Println(stu1 == stu6) // "false" }
可比較的結構體類型和其餘可比較的類型同樣,能夠用於 map 的 key 類型。
如今咱們有一個需求:用結構體存儲多個學生的信息。
咱們就能夠定義結構體數組來存儲,而後經過循環的方式,將結構體數組中的每一項進行輸出:
package main import "fmt" type student struct { id int name string score int } func main() { // 結構體數組 students := [3]student{ {101, "conan", 88}, {102, "kidd", 78}, {103, "lan", 98}, } // 打印結構體數組的每一項 for index, stu := range students { fmt.Println(index, stu.name) } }
用結構體切片存儲同理。
練習1:計算以上學生成績的總分。
package main import "fmt" type student struct { id int name string score int } func main() { // 結構體數組 students := [3]student{ {101, "conan", 88}, {102, "kidd", 78}, {103, "lan", 98}, } // 計算以上學生成績的總分 sum := students[0].score for i, stuLen := 1, len(students); i < stuLen; i++ { sum += students[i].score } fmt.Println("總分是:", sum) }
練習2:輸出以上學生成績中最高分。
package main import "fmt" type student struct { id int name string score int } func main() { // 結構體數組 students := [3]student{ {101, "conan", 88}, {102, "kidd", 78}, {103, "lan", 98}, } // 輸出以上學生成績中最高分 maxScore := students[0].score for i, stuLen := 1, len(students); i < stuLen; i++ { if maxScore < students[i].score { maxScore = students[i].score } } fmt.Println("最高分是:", maxScore) }
結構體做爲 map 的 value 示例以下:
package main import "fmt" type student struct { id int name string score int } func main0801() { // 結構體數組 students := [3]student{ {101, "conan", 88}, {102, "kidd", 78}, {103, "lan", 98}, } // 打印結構體數組的每一項 for index, stu := range students { fmt.Println(index, stu.name) } fmt.Println(students) // 計算以上學生成績的總分 sum := students[0].score for i, stuLen := 1, len(students); i < stuLen; i++ { sum += students[i].score } fmt.Println("總分是:", sum) // 輸出以上學生成績中最高分 maxScore := students[0].score for i, stuLen := 1, len(students); i < stuLen; i++ { if maxScore < students[i].score { maxScore = students[i].score } } fmt.Println("最高分是:", maxScore) } func main() { // 定義 map m := make(map[int]student) m[101] = student{101, "conan", 88} m[102] = student{102, "kidd", 78} m[103] = student{103, "lan", 98} fmt.Println(m) // map[101:{101 conan 88} 102:{102 kidd 78} 103:{103 lan 98}] for k, v := range m { fmt.Println(k, v) } }
結構體切片(本質上是切片)做爲 map 的 value 示例以下:
package main import "fmt" type student struct { id int name string score int } func main() { m := make(map[int][]student) m[101] = append(m[101], student{1, "conan", 88}, student{2, "kidd", 78}) m[102] = append(m[101], student{1, "lan", 98}, student{2, "blame", 66}) // 101 [{1 conan 88} {2 kidd 78}] // 102 [{1 conan 88} {2 kidd 78} {1 lan 98} {2 blame 66}] for k, v := range m { fmt.Println(k, v) } for k, v := range m { for i, data := range v { fmt.Println(k, i, data) } } }
你能夠像其它數據類型同樣將結構體類型做爲參數傳遞給函數:
結構體傳遞爲 值傳遞(形參單元和實參單元是不一樣的存儲區域,修改不會影響其它的值)
package main import "fmt" type student struct { id int name string score int } func foo(stu student) { stu.name = "lan" } func main() { stu := student{101, "conan", 88} fmt.Println(stu) // {101 conan 88} foo(stu) fmt.Println(stu) // {101 conan 88} }
經過以上程序,咱們知道:Go 函數給參數傳遞值的時候是以複製的方式進行的。複製傳值時,若是函數的參數是一個 struct 對象,將直接複製整個數據結構的副本傳遞給函數。
這有兩個問題:
函數內部沒法修改傳遞給函數的原始數據結構,它修改的只是原始數據結構拷貝後的副本;
若是傳遞的原始數據結構很大,完整地複製出一個副本開銷並不小。
因此,若是條件容許,應當給須要 struct 實例做爲參數的函數傳 struct 的指針。
PS:
- 結構體切片做爲函數參數是地址傳遞
- 結構體數組做爲函數參數是值傳遞
定義結構體,存儲5名學生,三門成績,求出每名學生的總成績和平均成績。
結構體定義示例:
type student struct { id int name string score []int }
package main import "fmt" type student struct { id int name string score []int } func main() { stus := []student{ {101, "小明", []int{100, 99, 94}}, {102, "小紅", []int{60, 123, 98}}, {103, "小剛", []int{90, 109, 81}}, {104, "小強", []int{55, 66, 99}}, {105, "小花", []int{123, 65, 89}}, } for _, stu := range stus { // 三門總成績 sum := 0 for _, value := range stu.score { sum += value } fmt.Printf("%s 的總成績爲: %d, 平均成績爲: %d\n", stu.name, sum, sum/len(stu.score)) } }
歡迎訪問個人我的網站:
李培冠博客:lpgit.com