<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] # 結構體 ## 結構體的定義 使用`type`和`struct`關鍵字來定義結構體,具體代碼格式如下: ~~~go type 類型名 struct { 字段名 字段類型 字段名 字段類型 … } ~~~ 其中: * 類型名:標識自定義結構體的名稱,在同一個包內不能重復。 * 字段名:表示結構體字段名。結構體中的字段名必須唯一。 * 字段類型:表示結構體字段的具體類型。 ~~~go type person struct { name string age int8 } ~~~ 同樣類型的字段也可以寫在一行, ~~~go type person1 struct { name, province string age int8 } ~~~ ## 結構體實例化 只有當結構體實例化時,才會真正地分配內存。也就是必須實例化后才能使用結構體的字段。 ~~~go var 結構體實例 結構體類型 ~~~ ### 基本實例化 ~~~go type person struct { name string age int8 } func main() { var p1 person p1.name = "小叮當" p1.age = 18 fmt.Printf("p1=%v\n", p1) //p1={小叮當18} fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"小叮當", age:18} } ~~~ 通過`.`來訪問結構體的字段(成員變量),例如`p1.name`和`p1.age`等。 ### 匿名結構體 在定義一些臨時數據結構等場景下還可以使用匿名結構體。 ~~~ package main import ( "fmt" ) func main() { var user struct{Name string; Age int} user.Name = "小叮當" user.Age = 18 fmt.Printf("%#v\n", user) } ~~~ ### 創建指針類型結構體 可以通過使用`new`關鍵字對結構體進行實例化,得到的是結構體的地址 ~~~go var p2 = new(person) fmt.Printf("%T\n", p2) //*main.person fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"",age:0} ~~~ 需要注意的是在Go語言中支持對結構體指針直接使用`.`來訪問結構體的成員。 ~~~go var p2 = new(person) p2.name = "小叮當" p2.age = 18 fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小叮當", age:18} ~~~ ### 取結構體的地址實例化 使用`&`對結構體進行取地址操作相當于對該結構體類型進行了一次`new`實例化操作。 ~~~go p3 := &person{} fmt.Printf("%T\n", p3) //*main.person fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"",age:0} p3.name = "小叮當" p3.age = 18 fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"小叮當",age:18} ~~~ `p3.name = "小叮當"`其實在底層是`(*p3).name = "小叮當"`,這是Go語言幫我們實現的語法糖。 ## 結構體初始化 沒有初始化的結構體,其成員變量都是對應其類型的零值。 ~~~go type person struct { name string age int8 } func main() { var p4 person fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"",age:0} } ~~~ ### 使用鍵值對初始化 使用鍵值對對結構體進行初始化時,鍵對應結構體的字段,值對應該字段的初始值。 ~~~go p5 := person{ name: "小叮當", age: 18, } fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小叮當", age:18} ~~~ 也可以對結構體指針進行鍵值對初始化 ~~~go p6 := &person{ name: "小叮當", city: "北京", age: 18, } fmt.Printf("p6=%#v\n", p6) ~~~ 當某些字段沒有初始值的時候,該字段可以不寫。此時,沒有指定初始值的字段的值就是該字段類型的零值。 ~~~go p7 := &person{ city: "北京", } fmt.Printf("p7=%#v\n", p7) ~~~ ### 使用值的列表初始化 初始化結構體的時候可以簡寫,也就是初始化的時候不寫鍵,直接寫值: ~~~go p8 := &person{ "小叮當", 18, } fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"小叮當",age:18} ~~~ 使用這種格式初始化時,需要注意: 1. 必須初始化結構體的所有字段。 2. 初始值的填充順序必須與字段在結構體中的聲明順序一致。 3. 該方式不能和鍵值初始化方式混用。 ## 結構體內存布局 結構體占用一塊連續的內存。 ~~~go type test struct { a int8 b int8 c int8 d int8 } n := test{ 1, 2, 3, 4, } fmt.Printf("n.a %p\n", &n.a) fmt.Printf("n.b %p\n", &n.b) fmt.Printf("n.c %p\n", &n.c) fmt.Printf("n.d %p\n", &n.d) ~~~ 輸出: ~~~bash n.a 0xc0000a0060 n.b 0xc0000a0061 n.c 0xc0000a0062 n.d 0xc0000a0063 ~~~ ### 空結構體 空結構體是不占用空間的。 ~~~go var v struct{} fmt.Println(unsafe.Sizeof(v)) // 0 ~~~ ### 面試題 ~~~ type student struct?{ ????Name?string ????Age??int } func pase_student()?map[string]*student?{ m?:=?make(map[string]*student) stus?:=?[]student{ ????????{Name:?"zhou",?Age:?24}, ????????{Name:?"li",?Age:?23}, ????????{Name:?"wang",?Age:?22}, ????} for _,?stu?:=?range?stus?{ ????????m[stu.Name]?=?&stu } return?m } func main()?{ students?:=?pase_student() fork,?v?:=?range?students?{ ????????fmt.Printf("key=%s,value=%v?\n",?k,?v) ????} } ~~~ output:&{wang 22} **解析:**因為 for 遍歷時,變量?`stu`?指針不變,每次遍歷僅進行 struct 值拷貝,故?`m[stu.Name]=&stu`?實際上一致指向同一個指針,最終該指針的值為遍歷的最后一個 struct 的值拷貝。形同如下代碼: ~~~ varstu?student for _,?stu?:=?range?stus?{ ????m[stu.Name]?=?&stu } ~~~ 修正方案,取數組中原始值的指針: ~~~ for i,?_?:=?range?stus?{ stu:=stus[i] ????m[stu.Name]?=?&stu } ~~~ ## 構造函數 Go語言的結構體沒有構造函數,我們可以自己實現。 例如,下方的代碼就實現了一個`person`的構造函數。 因為`struct`是值類型,如果結構體比較復雜的話,值拷貝性能開銷會比較大,所以該構造函數返回的是結構體指針類型。 ~~~go func newPerson(name, city string, age int8) *person { return &person{ name: name, age: age, } } ~~~ 調用構造函數 ~~~go p9 := newPerson("小叮當", 18) fmt.Printf("%#v\n", p9) //&main.person{name:"小叮當", age:18} ~~~ ## 方法和接收者 `方法(Method)`是一種作用于特定類型變量的函數。這種特定類型變量叫做`接收者(Receiver)`。接收者的概念就類似于其他語言中的`this`或者`self`。 格式如下: ~~~go func (接收者變量 接收者類型) 方法名(參數列表) (返回參數) { 函數體 } ~~~ 其中: * 接收者變量:接收者中的參數變量名在命名時,官方建議使用接收者類型名稱首字母的小寫,而不是`self`、`this`之類的命名。例如,`Person`類型的接收者變量應該命名為`p`,`Connector`類型的接收者變量應該命名為`c`等。 * 接收者類型:接收者類型和參數類似,可以是指針類型和非指針類型。 * 方法名、參數列表、返回參數:具體格式與函數定義相同。 ~~~go //Person 結構體 type Person struct { name string age int8 } //NewPerson 構造函數 func NewPerson(name string, age int8) *Person { return &Person{ name: name, age: age, } } //Dream func (p Person) Dream() { fmt.Printf("%s的夢想是學好Go語言!\n", p.name) } func main() { p1 := NewPerson("小叮當", 18) p1.Dream() } ~~~ `方法`與`函數`的區別是,函數不屬于任何類型,方法屬于特定的類型。 ### 指針類型的接收者 指針類型的接收者由一個結構體的指針組成,由于指針的特性,調用方法時修改接收者指針的任意成員變量,在方法結束后,修改都是有效的。這種方式就十分接近于其他語言中面向對象中的`this`或者`self` ~~~go // SetAge 設置p的年齡 // 使用指針接收者 func (p *Person) SetAge(newAge int8) { p.age = newAge } ~~~ 調用該方法: ~~~go func main() { p1 := NewPerson("小叮當", 18) fmt.Println(p1.age) // 18 p1.SetAge(99) fmt.Println(p1.age) // 99 } ~~~ ### 值類型的接收者 當方法作用于值類型接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值類型接收者的方法中可以獲取接收者的成員值,但修改操作只是針對副本,無法修改接收者變量本身。 ~~~go // SetAge2 設置p的年齡 // 使用值接收者 func (p Person) SetAge2(newAge int8) { p.age = newAge } func main() { p1 := NewPerson("小叮當", 18) p1.Dream() fmt.Println(p1.age) // 18 p1.SetAge2(99) // (*p1).SetAge2(99) fmt.Println(p1.age) // 18 } ~~~ ### 什么時候應該使用指針類型接收者 1. 需要修改接收者中的值 2. 接收者是拷貝代價比較大的大對象 3. 保證一致性,如果有某個方法使用了指針接收者,那么其他的方法也應該使用指針接收者。 ## 任意類型添加方法 在Go語言中,接收者的類型可以是任何類型,不僅僅是結構體,任何類型都可以擁有方法。 舉個例子,我們基于內置的`int`類型使用type關鍵字可以定義新的自定義類型,然后為我們的自定義類型添加方法。 ~~~go //MyInt 將int定義為自定義MyInt類型 type MyInt int //SayHello 為MyInt添加一個SayHello的方法 func (m MyInt) SayHello() { fmt.Println("Hello, 我是一個int。") } func main() { var m1 MyInt m1.SayHello() //Hello, 我是一個int。 m1 = 100 fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt } ~~~ **注意事項:**非本地類型不能定義方法,也就是說我們不能給別的包的類型定義方法。 ## 結構體的匿名字段 結構體允許其成員字段在聲明時沒有字段名而只有類型,這種沒有名字的字段就稱為匿名字段。 ~~~go //Person 結構體Person類型 type Person struct { string int } func main() { p1 := Person{ "小叮當", 18, } fmt.Printf("%#v\n", p1) //main.Person{string:"小叮當", int:18} fmt.Println(p1.string, p1.int) //北京 18 } ~~~ **注意:**這里匿名字段的說法并不代表沒有字段名,而是默認會采用類型名作為字段名,結構體要求字段名稱必須唯一,因此一個結構體中同種類型的匿名字段只能有一個。 ## 嵌套結構體 一個結構體中可以嵌套包含另一個結構體或結構體指針 ![](https://img.kancloud.cn/be/0a/be0a81706aaf5b7cfcb82809426be1cb_789x671.png) ### 嵌套匿名字段 ![](https://img.kancloud.cn/f8/07/f8075a0f5c708a33dc1a26ee33f90aa6_1421x742.png) 字段查找順序:先在結構體中查找該字段,找不到再去嵌套的匿名字段中查找 ### 嵌套結構體的字段名沖突 嵌套結構體內部可能存在相同的字段名,在這種情況下為了避免歧義需要通過指定具體的內嵌結構體字段名。 ![](https://img.kancloud.cn/c4/ba/c4ba508662d50234ffe3b388addeb31e_1024x792.png) ## 結構體的“繼承” ![](https://img.kancloud.cn/c4/ba/c4ba508662d50234ffe3b388addeb31e_1024x792.png) ## 結構體字段的可見性 結構體中字段大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)。 ## 結構體與JSON序列化 ``` type School struct { SchName string ? ? Class ? []*Class } type Class struct { ClassNum int } func main() { school := &School{ SchName: "小叮當", Class: make([]*Class, 0, 2), ? ? } for i := 1; i <= 2; i++ { c := &Class{ ? ? ? ? ? ? ClassNum: i, } school.Class = append(school.Class, c) } data, err := json.Marshal(school) if err != nil { ? ? fmt.Println("json marshal failed") return ?} ?fmt.Printf("json:%s\n", data) //{"SchName":"小叮當","Class":[{"ClassNum":1},{"ClassNum":2}]} jstr := `{"SchName":"小叮當","Class":[{"ClassNum":1},{"ClassNum":2}]}` c := &School{} err = json.Unmarshal([]byte(jstr), c) if err != nil { ? ? ?fmt.Println("json marshal failed") return } fmt.Printf("%#v", c) //&main.School{SchName:"小叮當", Class:[]*main.Class{(*main.Class)(0xc000014218), (*main.Class)(0xc000014240)}} } ``` ## 結構體標簽(Tag) `Tag`是結構體的元信息,可以在運行的時候通過反射的機制讀取出來。`Tag`在結構體字段的后方定義,由一對**反引號**包裹起來,具體的格式如下: ~~~ `key1:"value1" key2:"value2"` ~~~ 結構體tag由一個或多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。同一個結構體字段可以設置多個鍵值對tag,不同的鍵值對之間使用空格分隔。 ![](https://img.kancloud.cn/6a/03/6a03c5f80f3c9c4955d3dbe89e47587f_566x745.png) **注意事項:**為結構體編寫`Tag`時,必須嚴格遵守鍵值對的規則。結構體標簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,通過反射也無法正確取值。例如不要在key和value之間添加空格。 ## 結構體和方法補充點 因為slice和map這兩種數據類型都包含了指向底層數據的指針 ``` type Person struct { name string age int8 dreams []string } func (p *Person) SetDreams(dreams []string) { p.dreams = dreams } func main() { p1 := Person{name: "小叮當", age: 18} data := []string{"吃飯", "睡覺", "變魔術"} p1.SetDreams(data) // 下面的這個操作會改變 p1的dreams data[1] = "做夢" fmt.Println(p1.dreams) } //如果不改變,那就用copy去復制一份數據,保存在新的內存當中 func (p *Person) SetDreams(dreams []string) { p.dreams = make([]string, len(dreams)) copy(p.dreams, dreams) } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看