Golang 结构体
前言
結(jié)構(gòu)體是一種聚合的數(shù)據(jù)類型,是由零個(gè)或多個(gè)任意類型的值聚合成的實(shí)體。每個(gè)值稱為結(jié)構(gòu)體的成員。
用結(jié)構(gòu)體的經(jīng)典案例:學(xué)校的學(xué)生信息,每個(gè)學(xué)生信息包含一個(gè)唯一的學(xué)生學(xué)號(hào)、學(xué)生的名字、學(xué)生的性別、家庭住址等等。所有的這些信息都需要綁定到一個(gè)實(shí)體中,可以作為一個(gè)整體單元被復(fù)制,作為函數(shù)的參數(shù)或返回值,或者是被存儲(chǔ)到數(shù)組中,等等。
結(jié)構(gòu)體也是值類型,因此可以通過(guò) new 函數(shù)來(lái)創(chuàng)建。
組成結(jié)構(gòu)體類型的那些數(shù)據(jù)稱為字段(fields)。字段有以下特性:
- 字段擁有自己的類型和值。
- 字段名必須唯一。
- 字段的類型也可以是結(jié)構(gòu)體,甚至是字段所在結(jié)構(gòu)體的類型。
關(guān)于 Go 語(yǔ)言的類(class)
Go 語(yǔ)言中沒(méi)有“類”的概念,也不支持“類”的繼承等面向?qū)ο蟮母拍睢o 語(yǔ)言的結(jié)構(gòu)體與“類”都是復(fù)合結(jié)構(gòu)體,但 Go 語(yǔ)言中結(jié)構(gòu)體的內(nèi)嵌配合接口比面向?qū)ο缶哂懈叩臄U(kuò)展性和靈活性。
Go 語(yǔ)言不僅認(rèn)為結(jié)構(gòu)體能擁有方法,且每種自定義類型也可以擁有自己的方法。
結(jié)構(gòu)體的定義
使用關(guān)鍵字 type 可以將各種基本類型定義為自定義類型,基本類型包括整型、字符串、布爾等。結(jié)構(gòu)體是一種復(fù)合的基本類型,通過(guò) type 定義為自定義類型后,使結(jié)構(gòu)體更便于使用。
結(jié)構(gòu)體的定義格式如下:
type 結(jié)構(gòu)體類型名 struct {字段1 字段1類型字段2 字段2類型… }其中:
1、結(jié)構(gòu)體類型名:標(biāo)識(shí)自定義結(jié)構(gòu)體的名稱,在同一個(gè)包內(nèi)不能重復(fù)。
2、字段1:表示結(jié)構(gòu)體字段名。結(jié)構(gòu)體中的字段名必須唯一。
3、字段1類型:表示結(jié)構(gòu)體字段的具體類型。
舉個(gè)例子,我們定義一個(gè) Student(學(xué)生)結(jié)構(gòu)體,代碼如下:
type Student struct{id intname stringage intgender int // 0 表示女生,1 表示男生addr string }在這里,Student 的地位等價(jià)于 int、byte、bool、string 等類型
通常一行對(duì)應(yīng)一個(gè)結(jié)構(gòu)體成員,成員的名字在前,類型在后
不過(guò)如果相鄰的成員類型如果相同的話可以被合并到一行:
type Student struct{id intname stringage, gender intaddr string }這樣我們就擁有了一個(gè) Student 的自定義類型,它有 id、name、age等字段。
這樣我們使用這個(gè) Student 結(jié)構(gòu)體就能夠很方便的在程序中表示和存儲(chǔ)學(xué)生信息了。
遞歸結(jié)構(gòu)體
結(jié)構(gòu)體類型可以通過(guò)引用自身來(lái)定義。這在定義鏈表或二叉樹(shù)的元素(通常叫節(jié)點(diǎn))時(shí)特別有用,此時(shí)節(jié)點(diǎn)包含指向臨近節(jié)點(diǎn)的鏈接(地址)。
如下所示,鏈表中的 su,樹(shù)中的 ri 和 le 分別是指向別的節(jié)點(diǎn)的指針。
鏈表
type Node struct {data float64su *Node }雙向鏈表
type Node struct {pr *Nodedata float64su *Node }二叉樹(shù)
type Tree struct {le *Treedata float64ri *Tree }結(jié)構(gòu)體的實(shí)例化
結(jié)構(gòu)體的定義只是一種內(nèi)存布局的描述,只有當(dāng)結(jié)構(gòu)體實(shí)例化時(shí),才會(huì)真正地分配內(nèi)存,因此必須在定義結(jié)構(gòu)體并實(shí)例化后才能使用結(jié)構(gòu)體的字段。
實(shí)例化就是根據(jù)結(jié)構(gòu)體定義的格式創(chuàng)建一份與格式一致的內(nèi)存區(qū)域,結(jié)構(gòu)體實(shí)例與實(shí)例間的內(nèi)存是完全獨(dú)立的。
Go語(yǔ)言可以通過(guò)多種方式實(shí)例化結(jié)構(gòu)體,根據(jù)實(shí)際需要可以選用不同的寫(xiě)法。
基本的實(shí)例化形式
結(jié)構(gòu)體本身也是一種類型,我們可以像聲明內(nèi)置類型一樣使用 var 關(guān)鍵字聲明結(jié)構(gòu)體類型。
基本實(shí)例化格式如下:
var 結(jié)構(gòu)體實(shí)例 結(jié)構(gòu)體類型對(duì) Student 進(jìn)行實(shí)例化,代碼如下:
type Student struct{id intname stringage intgender int // 0 表示女生,1 表示男生addr string }func main() {var stu1 Studentstu1.id = 120100stu1.name = "Conan"stu1.age = 18stu1.gender = 1fmt.Println("stu1 = ", stu1) // stu1 = {120100 Conan 18 1 } }注意:沒(méi)有賦值的字段默認(rèn)為該字段類型的零值,此時(shí) addr = ""
我們可以通過(guò) 點(diǎn) "." 的方式來(lái)訪問(wèn)結(jié)構(gòu)體的成員變量,如 stu1.name,結(jié)構(gòu)體成員變量的賦值方法與普通變量一致。
創(chuàng)建指針類型的結(jié)構(gòu)體
Go 語(yǔ)言中,還可以使用 new 關(guān)鍵字對(duì)類型(包括結(jié)構(gòu)體、整型、浮點(diǎn)數(shù)、字符串等)進(jìn)行實(shí)例化,結(jié)構(gòu)體在實(shí)例化后會(huì)形成指針類型的結(jié)構(gòu)體。
使用 new 的格式如下:
變量名 := new(類型)Go 語(yǔ)言讓我們可以像訪問(wèn)普通結(jié)構(gòu)體一樣使用 點(diǎn)"." 來(lái)訪問(wèn)結(jié)構(gòu)體指針的成員,例如:
type Student struct{id intname stringage intgender int // 0 表示女生,1 表示男生addr string }func main() {stu2 := new(Student)stu2.id = 120101stu2.name = "Kidd"stu2.age = 23stu2.gender = 1fmt.Println("stu2 = ", stu2) // stu2 = &{120101 Kidd 23 1 } }經(jīng)過(guò) new 實(shí)例化的結(jié)構(gòu)體實(shí)例在成員賦值上與基本實(shí)例化的寫(xiě)法一致。
注意:在 Go 語(yǔ)言中,訪問(wèn)結(jié)構(gòu)體指針的成員變量時(shí)可以繼續(xù)使用 點(diǎn)".",這是因?yàn)?Go 語(yǔ)言為了方便開(kāi)發(fā)者訪問(wèn)結(jié)構(gòu)體指針的成員變量,使用了語(yǔ)法糖(Syntactic sugar)技術(shù),將 stu2.name 形式轉(zhuǎn)換為 (*stu2).name。
取結(jié)構(gòu)體的地址實(shí)例化
在 Go 語(yǔ)言中,對(duì)結(jié)構(gòu)體進(jìn)行 & 取地址操作時(shí),視為對(duì)該類型進(jìn)行一次 new 的實(shí)例化操作,取地址格式如下:
變量名 := &結(jié)構(gòu)體類型{}取地址實(shí)例化是最廣泛的一種結(jié)構(gòu)體實(shí)例化方式,具體代碼如下:
type Student struct{id intname stringage intgender int // 0 表示女生,1 表示男生addr string }func main() {stu3 := &Student{}stu3.id = 120102stu3.name = "Lan"stu3.age = 18stu3.gender = 0fmt.Println("stu3 = ", stu3) // stu3 = &{120102 Lan 18 0 } }結(jié)構(gòu)體的初始化
結(jié)構(gòu)體在實(shí)例化時(shí)可以直接對(duì)成員變量進(jìn)行初始化,初始化有兩種形式分別是以字段“鍵值對(duì)”形式和多個(gè)值的列表形式。
鍵值對(duì)形式的初始化適合選擇性填充字段較多的結(jié)構(gòu)體,多個(gè)值的列表形式適合填充字段較少的結(jié)構(gòu)體。
特別地,還有一種初始化匿名結(jié)構(gòu)體。
使用“鍵值對(duì)”初始化結(jié)構(gòu)體
結(jié)構(gòu)體可以使用“鍵值對(duì)”(Key value pair)初始化字段,每個(gè)“鍵”(Key)對(duì)應(yīng)結(jié)構(gòu)體中的一個(gè)字段,鍵的“值”(Value)對(duì)應(yīng)字段需要初始化的值。
鍵值對(duì)的填充是可選的,不需要初始化的字段可以不填入初始化列表中。
結(jié)構(gòu)體實(shí)例化后字段的默認(rèn)值是字段類型的零值,例如 ,數(shù)值為 0、字符串為 “”(空字符串)、布爾為 false、指針為 nil 等。
鍵值對(duì)初始化的格式如下:
變量名 := 結(jié)構(gòu)體類型名{字段1: 字段1的值,字段2: 字段2的值,... }注意:
1、字段名只能出現(xiàn)一次。
2、鍵值之間以 : 分隔,鍵值對(duì)之間以 , 分隔。
使用鍵值對(duì)形式初始化結(jié)構(gòu)體的代碼如下:
stu4 := Student{id: 120103,name: "Gin",age: 25,gender: 1,addr: "unknown", } fmt.Println("stu4 = ", stu4) // stu4 = {120103 Gin 25 1 unknown}使用多個(gè)值的列表初始化結(jié)構(gòu)體
Go語(yǔ)言可以在“鍵值對(duì)”初始化的基礎(chǔ)上忽略“鍵”,也就是說(shuō),可以使用多個(gè)值的列表初始化結(jié)構(gòu)體的字段。
多個(gè)值使用逗號(hào)分隔初始化結(jié)構(gòu)體,例如:
變量名 := 結(jié)構(gòu)體類型名{字段1的值,字段2的值,... }注意:
1、必須初始化結(jié)構(gòu)體的所有字段。
2、每一個(gè)初始值的填充順序必須與字段在結(jié)構(gòu)體中的聲明順序一致。
3、鍵值對(duì)與值列表的初始化形式不能混用。
使用多個(gè)值列表初始化結(jié)構(gòu)體的代碼如下:
stu5 := Student{120104,"Kogorou",38,1,"毛利偵探事務(wù)所", } fmt.Println("stu5 = ", stu5) // stu5 = {120104 Kogorou 38 1 毛利偵探事務(wù)所}初始化匿名結(jié)構(gòu)體
匿名結(jié)構(gòu)體沒(méi)有類型名稱,無(wú)須通過(guò) type 關(guān)鍵字定義就可以直接使用。
例如:
package mainimport ("fmt" )func main() {var user struct{name string; age int}user.name = "Conan"user.age = 18fmt.Println("user = ", user) // user = {Conan 18} }結(jié)構(gòu)體的賦值與比較
結(jié)構(gòu)體的賦值
當(dāng)使用 = 對(duì)結(jié)構(gòu)體賦值時(shí),更改其中一個(gè)結(jié)構(gòu)體的值不會(huì)影響另外的值:
package mainimport ("fmt" )type Student struct {id intname stringage intgender int // 0 表示女生,1 表示男生addr string }func main() {var stu1 Studentstu1.id = 120100stu1.name = "Conan"stu1.age = 18stu1.gender = 1stu6 := stu1fmt.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 } }結(jié)構(gòu)體的比較
如果結(jié)構(gòu)體的全部成員都是可以比較的,那么結(jié)構(gòu)體也是可以比較的;如果結(jié)構(gòu)體中存在不可比較的成員變量,比如說(shuō)切片、map等,那么結(jié)構(gòu)體就不能比較。這時(shí)如果強(qiáng)行用 ==、!= 來(lái)進(jìn)行判斷的話,程序會(huì)直接報(bào)錯(cuò),我們可以用 DeepEqual 來(lái)進(jìn)行深度比較。
如果結(jié)構(gòu)體的全部成員都是可以比較的,那么兩個(gè)結(jié)構(gòu)體將可以使用 == 或 != 運(yùn)算符進(jìn)行比較。
相等比較運(yùn)算符 == 將比較兩個(gè)結(jié)構(gòu)體的每個(gè)成員,因此下面兩個(gè)比較的表達(dá)式是等價(jià)的:
type Student struct {id intname string }func main() {var stu1 Studentstu1.id = 120100stu1.name = "Conan"stu6 := stu1stu6.name = "柯南"fmt.Println(stu1.id == stu6.id && stu1.name == stu6.name) // "false"fmt.Println(stu1 == stu6) // "false" }可比較的結(jié)構(gòu)體類型和其他可比較的類型一樣,可以用于 map 的 key 類型。
結(jié)構(gòu)體數(shù)組和切片
現(xiàn)在我們有一個(gè)需求:用結(jié)構(gòu)體存儲(chǔ)多個(gè)學(xué)生的信息。
我們就可以定義結(jié)構(gòu)體數(shù)組來(lái)存儲(chǔ),然后通過(guò)循環(huán)的方式,將結(jié)構(gòu)體數(shù)組中的每一項(xiàng)進(jìn)行輸出:
package mainimport "fmt"type student struct {id intname stringscore int }func main() {// 結(jié)構(gòu)體數(shù)組students := [3]student{{101, "conan", 88},{102, "kidd", 78},{103, "lan", 98},}// 打印結(jié)構(gòu)體數(shù)組的每一項(xiàng)for index, stu := range students {fmt.Println(index, stu.name)} }用結(jié)構(gòu)體切片存儲(chǔ)同理。
練習(xí)1:計(jì)算以上學(xué)生成績(jī)的總分。
package mainimport "fmt"type student struct {id intname stringscore int }func main() {// 結(jié)構(gòu)體數(shù)組students := [3]student{{101, "conan", 88},{102, "kidd", 78},{103, "lan", 98},}// 計(jì)算以上學(xué)生成績(jī)的總分sum := students[0].scorefor i, stuLen := 1, len(students); i < stuLen; i++ {sum += students[i].score}fmt.Println("總分是:", sum) }練習(xí)2:輸出以上學(xué)生成績(jī)中最高分。
package mainimport "fmt"type student struct {id intname stringscore int }func main() {// 結(jié)構(gòu)體數(shù)組students := [3]student{{101, "conan", 88},{102, "kidd", 78},{103, "lan", 98},}// 輸出以上學(xué)生成績(jī)中最高分maxScore := students[0].scorefor i, stuLen := 1, len(students); i < stuLen; i++ {if maxScore < students[i].score {maxScore = students[i].score}}fmt.Println("最高分是:", maxScore) }結(jié)構(gòu)體作為 map 的 value
結(jié)構(gòu)體作為 map 的 value 示例如下:
package mainimport "fmt"type student struct {id intname stringscore int }func main0801() {// 結(jié)構(gòu)體數(shù)組students := [3]student{{101, "conan", 88},{102, "kidd", 78},{103, "lan", 98},}// 打印結(jié)構(gòu)體數(shù)組的每一項(xiàng)for index, stu := range students {fmt.Println(index, stu.name)}fmt.Println(students)// 計(jì)算以上學(xué)生成績(jī)的總分sum := students[0].scorefor i, stuLen := 1, len(students); i < stuLen; i++ {sum += students[i].score}fmt.Println("總分是:", sum)// 輸出以上學(xué)生成績(jī)中最高分maxScore := students[0].scorefor i, stuLen := 1, len(students); i < stuLen; i++ {if maxScore < students[i].score {maxScore = students[i].score}}fmt.Println("最高分是:", maxScore) }func main() {// 定義 mapm := 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)} }結(jié)構(gòu)體切片作為 map 的 value
結(jié)構(gòu)體切片(本質(zhì)上是切片)作為 map 的 value 示例如下:
package mainimport "fmt"type student struct {id intname stringscore 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)}} }結(jié)構(gòu)體作為函數(shù)參數(shù)
你可以像其它數(shù)據(jù)類型一樣將結(jié)構(gòu)體類型作為參數(shù)傳遞給函數(shù):
結(jié)構(gòu)體傳遞為 值傳遞(形參單元和實(shí)參單元是不同的存儲(chǔ)區(qū)域,修改不會(huì)影響其它的值)
package mainimport "fmt"type student struct {id intname stringscore 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} }通過(guò)以上程序,我們知道:Go 函數(shù)給參數(shù)傳遞值的時(shí)候是以復(fù)制的方式進(jìn)行的。復(fù)制傳值時(shí),如果函數(shù)的參數(shù)是一個(gè) struct 對(duì)象,將直接復(fù)制整個(gè)數(shù)據(jù)結(jié)構(gòu)的副本傳遞給函數(shù)。
這有兩個(gè)問(wèn)題:
函數(shù)內(nèi)部無(wú)法修改傳遞給函數(shù)的原始數(shù)據(jù)結(jié)構(gòu),它修改的只是原始數(shù)據(jù)結(jié)構(gòu)拷貝后的副本;
如果傳遞的原始數(shù)據(jù)結(jié)構(gòu)很大,完整地復(fù)制出一個(gè)副本開(kāi)銷(xiāo)并不小。
所以,如果條件允許,應(yīng)當(dāng)給需要 struct 實(shí)例作為參數(shù)的函數(shù)傳 struct 的指針。
PS:
練習(xí)
定義結(jié)構(gòu)體,存儲(chǔ)5名學(xué)生,三門(mén)成績(jī),求出每名學(xué)生的總成績(jī)和平均成績(jī)。
結(jié)構(gòu)體定義示例:
type student struct {id intname stringscore []int } package mainimport "fmt"type student struct {id intname stringscore []int }func main() {stus := []student{{101, "小明", []int{100, 99, 94}},{102, "小紅", []int{60, 123, 98}},{103, "小剛", []int{90, 109, 81}},{104, "小強(qiáng)", []int{55, 66, 99}},{105, "小花", []int{123, 65, 89}},}for _, stu := range stus {// 三門(mén)總成績(jī)sum := 0for _, value := range stu.score {sum += value}fmt.Printf("%s 的總成績(jī)?yōu)? %d, 平均成績(jī)?yōu)? %d\n", stu.name, sum, sum/len(stu.score))} }李培冠博客
歡迎訪問(wèn)我的個(gè)人網(wǎng)站:
李培冠博客:lpgit.com
總結(jié)
以上是生活随笔為你收集整理的Golang 结构体的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 车间生产管理(一)· 产线流程控制及产品
- 下一篇: Apollo Planning决策规划算