Go語言入門-struct結構

Go語言入門-struct結構

關鍵字type

Go語言中使用type關鍵字自定義類型。

The new type is called a defined type. It is different from any other type, including the type it is created from.

語法

TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
AliasDecl = identifier "=" Type .
  • 示例1
    演示最簡單的自定義類型 type newType xxx
type newInt int

func main() {
    var a newInt
    var b int
    fmt.Printf("%T %s %#v\n", a, reflect.TypeOf(a), a)
    fmt.Printf("%T %s %#v\n", b, reflect.TypeOf(b), b)
    //類型不同無法判等
    //fmt.Println(b == a)//Invalid operation: b == a (mismatched types int and newInt)
}
/**
output:
main.newInt main.newInt 0
int int 0
 */

以上自定義一個類型爲newInt,newInt的類型看上去和int沒啥區別,但確確實實newInt是一個自定義的新類型,雖然和int具備相同的內存存儲結構。newInt表示的時候main包下面的newInt 。int是內置 類型int。

Go語言中使用type關鍵對已有類型取別名

使用關鍵字type定義自定義類型,包括基於基礎類型、結構體類型、函數類型、數組等類型進行定義。

語法

AliasDecl = identifier "=" Type .

類型別名,意味着對於某一個類型,再取一個名字,只是名字的區別,最基本的類型還是最初始的類型。type classAliasName type
來個示例看看。

  • 示例2
type aliasForInt = int

func main() {
    var a aliasForInt
    var b int
    fmt.Printf("%T %s %#v\n", a, reflect.TypeOf(a), a)
    fmt.Printf("%T %s %#v\n", b, reflect.TypeOf(b), b)
    fmt.Println(a == b)
}
/**
int int 0
int int 0
true
 */

類型別名實際上和原類型是相同類型。經常使用的類型別名有:rune 和 byte。

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

1. 自定義類型是新的類型,類型別名只是一個類型的另一個名字
2. 自定義類型和原類型不支持判等,類型別名支持判等。

struct結構體

簡介

go語言中沒有使用type和struct關鍵字定義命名結構體,用來把多個類型的字段安先後順序組合成一個符合類型,和C語言的struct比較類似。
結構體由一組元素組成,這些元素可以稱爲字段,每個字段都有一個名稱和一個類型(以及一個tag)。字段名可以顯式指定(IdentifierList)或隱式指定(EmbeddedField)。在結構體中,非空白字段名必須是惟一的。

A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-blank field names must be unique.

語法

//官方語法
StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag           = string_lit .
//簡單表示
struct {
	fieldName TYPE1
	fieldName2 TYPE2
	...
	fieldNameN TYPEN
	_ TYPE //匿名類型
}

未命名結構體

  • 示例3
    定義一個類型一個結構體類型的變量b, 變量b的類型是struct { name string; age int }。
func main() {
    var b struct {
        name string
        age int
    }
    fmt.Printf("%#v %s\n", b, reflect.TypeOf(b))
}
/**
output:
struct { name string; age int }{name:"", age:0} struct { name string; age int }
 */

經常把struct {…}稱爲未命名的結構體類型,或者是匿名結構體。

命名結構體

使用關鍵字key定義一個類型animal。該animal是一個結構體,該結構體第一個域是name string類型,第二個域是age unint類型。

  • 示例4
type animal struct {
    name string
    age uint
}
func main() {
    a := animal{
        name: "a",
        age:  1,
    }
    fmt.Printf("%#v %s\n", a, reflect.TypeOf(a))
}
/**
output:
main.animal{name:"a", age:0x1} main.animal
 */

這樣我們就對結構體有了最基本的認識。

結構體實例化

基本的結構體聲明和定義

結構體類型,屬於自定義類型,也是一種類型。我們也可以使用var關鍵字聲明結構體類型。

  • 示例5
type animal2 struct {
    name string
    age uint
}
func main() {
    var a animal2
    a.name = "test"
    fmt.Printf("%#v %s\n", a, reflect.TypeOf(a))
}
/**
output:
main.animal2{name:"test", age:0x0} main.animal2
 */

結構體變量初始化

  1. 表達式列表初始化-順序
    按照結構體域所有字段域進行窮舉顯示初始化。
  • 示例6
type animal3 struct {
    name string
    age uint
    sex bool
}
func main() {
    var a = animal3{
        "wo", 15, true,
    }
    fmt.Println(a)
    b := animal3{
        "wo", 15, true,
    }
    fmt.Println(b)
    // too few values in animal3 literal
    //var c = animal3{
    //    "wo",
    //}
}
/**
output:
{wo 15 true}
{wo 15 true}
 */

1. 必須顯示初始化所有域字段
2. 初始化順序同定義必須一致,不然類型可能匹配不上,或者複製錯誤。

  1. 表達式初始化(鍵值)-無序
    通過key-value的方式對結構體進行無序的初始化,也可以初始化部分,以下例子作爲演示
  • 示例7
type animal4 struct {
    name string
    age uint
    sex bool
}

func main() {
    var a = animal4{
        name: "name1",
        age:  0,
        sex:  false,
    }
    fmt.Println(a)
    var b = animal4{
        age: 10,
    }
    fmt.Println(b)
    var c = animal4{
    }
    fmt.Println(c)
}
/**
output:
{name1 0 false}
{ 10 false}
{ 0 false}
 */

結構體使用

訪問結構體中的一個變量名, 用 “.” 來連接,(*T).field 的表達和T.field一致

  • 示例8
/*
結構體的使用
 */
type animal5 struct {
    name string
    age uint
    sex bool
}

func main() {
    a := animal5{
        name: "boyyyy",
        age:  30,
        sex:  false,
    }
    b := animal5{
        name: "girl",
        age:  1,
        sex:  true,
    }
    //訪問結構體域值
    fmt.Printf("a.name=[%s] a.age=[%d] a.sex=[%v]\n", a.name, a.age, a.sex)
    //c是b的地址因此c指向b
    c := &b
    fmt.Printf("a = [ %p |%#v ]\t\t\t\tb = [ %p | %#v] \n", &a, a, &b, b)
    a.age = 10
    b.name = "girrrrrl"
    //d是通過b構建的新內存塊
    d := b
    fmt.Printf("a = [ %p |%#v ]\t\t\t\tb = [ %p | %#v] \n", &a, a, &b, b)
    fmt.Printf("b = [ %p |%#v ]\t\t\t\td = [ %p | %#v] \n", &b, b, &d, d)
    c.name = "girllll"
    fmt.Printf("b = [ %p |%#v ]\t\t\t\tc = [ %p | %#v] \n", &b, b, c, c)
    //(*T).field == T.field  --->語法糖
    (*c).name = "ggggirl"
    fmt.Printf("b = [ %p |%#v ]\t\t\t\tc = [ %p | %#v] \n", &b, b, c, c)
}
/**
備註:地址值在不同電腦執行可能不一樣
output:
a.name=[boyyyy] a.age=[30] a.sex=[false]
a = [ 0xc0000044a0 |main.animal5{name:"boyyyy", age:0x1e, sex:false} ]				b = [ 0xc0000044c0 | main.animal5{name:"girl", age:0x1, sex:true}]
a = [ 0xc0000044a0 |main.animal5{name:"boyyyy", age:0xa, sex:false} ]				b = [ 0xc0000044c0 | main.animal5{name:"girrrrrl", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"girrrrrl", age:0x1, sex:true} ]				d = [ 0xc000004540 | main.animal5{name:"girrrrrl", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"girllll", age:0x1, sex:true} ]				c = [ 0xc0000044c0 | &main.animal5{name:"girllll", age:0x1, sex:true}]
b = [ 0xc0000044c0 |main.animal5{name:"ggggirl", age:0x1, sex:true} ]				c = [ 0xc0000044c0 | &main.animal5{name:"ggggirl", age:0x1, sex:true}]
*/
  1. 結構體類型賦值是值傳遞,因爲這會重新分配新的內存,以上例子(d := b)中b和d的地址分別是0xc0000044c00xc000004540 。因此會用性能損耗。
  2. 可以通過定義指針變量,來避免值傳遞,但是當對c修改時會影響b,反之修改b也是影響c指向的值,以上例子中c的值是0xc0000044c0,實際上是b的地址
  3. 可以使用(*T).field 的或者T.field來獲取值,或者修改值。類型+’.’+field

匿名的字段

結構體定義字段域時,只有類型名沒有字段名的字段叫做匿名字段。匿名字段只有類型沒有字段名,意味着同一個類型的匿名字段只能有一個,不能重複。
匿名字段可以模擬面向對象中的繼承,但是並不是真正的繼承。

  1. 匿名字段的定義
  • 示例9
/**
匿名字段
 */
type Person struct {
    name string
    agt int
    id string
    addr string
}
type Employee struct {
    Person
    salary int
    int
    //Duplicate field 'int'
    //int
    //int* //Duplicate field 'int' 
    addr string
}
func main() {
    e := Employee{
        Person: Person{
            name:   "name",
            agt:    10,
            id:     "xxxx001",
            addr:   "person addr",
        },
        salary: 49,
        int:    333,
        addr:   "employee",
    }
    fmt.Printf("%#v \n", e)
}
/**
output:
main.Employee{Person:main.Person{name:"name", agt:10, id:"xxxx001", addr:"person addr"}, salary:49, int:333, addr:"employee"}
*/

*匿名字段可以是結構體,也可以是普通的內置類型。就想結構體類型Employee 第三個字段int,只定義了類型int,沒有指定變量,因此int類型的匿名字段只能存在一個。包括int ,也不行。負責就報【Duplicate field ‘int’】的錯誤
不能存在多個同一類型和其類型的指針的匿名變量。
實際上來說初始化的時候可以使用 int:xxx進行初始化。也就是【類型名:值】的方式進行初始化。

除接口、指針、多記指針意外的任何命名類型都可以作爲匿名字段

  • 示例10
type myInt int
type myIntP *myInt
type d struct {
    myInt
    //myIntP//Embedded type cannot be a pointer
    //*myInt//Duplicate field 'myInt'
}

因未命名類型沒有名字標識,無法作爲匿名字段。????—還是沒有搞明白。。。TODO

  1. 匿名字段訪問

相同字段採用最外層優先訪問,類重載類似,這樣也可以模擬出繼承的特性,子類可以直接使用父類的成員變量。但是一旦命名重複,那麼需要顯示使用字段名訪問。

  • 示例11
type file struct {
    name string
}

type data struct {
    file
    name string
    log
}

func main() {
    d := data{
        file: file{
            name: "filename",
        },
        name: "dataname",
    }
    fmt.Printf("%#v\n", d)
    //最外層的name
    d.name = "dataname2"
    //使用匿名字段的log.name1
    d.name1 = "logname1"
    //當字段名重複以後必須顯示的進行使用d.匿名字段類型.字段值
    d.file.name = "filename"
    fmt.Printf("%#v\n", d)
}
/**
output:
main.data{file:main.file{name:"filename"}, name:"dataname", log:main.log{name1:""}}
main.data{file:main.file{name:"filename"}, name:"dataname2", log:main.log{name1:"logname1"}}
 */

使用匿名字段就意味着存在命名重複的情況,默認情況下,編譯優先查找顯示的命名字段,然後如果匿名字段不存在,就開始在匿名字段成員中查找字段,當匿名字段的子項和命名字段同名,就需要手工使用顯示字段名。就像上面例子中:d.file.name = "filename"是顯示的使用。因爲data類型中游name字段。

字段標籤

Tag字段標籤是go中新增加的特性,是對字段進行描述的元數據。可以再運行的時候通過反射的機制讀取。Tag在結構體字段的後面定義,通過反引號標記。

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag.

tag語法

struct {
    field fileType `key1:"value" key:"value2"`
}
  1. Tag的反射引用
  • 示例12
    簡單演示,通過反射獲取tag
type user struct {
    name string `暱稱`
    sex byte `性別`
}

func main() {
    u := user{
        name: "u_name",
        sex:  0,
    }
    v := reflect.ValueOf(u)
    t := v.Type()
    for i, n := 0, t.NumField(); i < n; i++ {
        //打印每個字段的tag和對應的值
        fmt.Printf("%s: %v\n", t.Field(i).Tag, v.Field(i))
    }
}
/**
output:
暱稱: u_name
性別: 0
 */
  1. 結構體的tag可以由一個或者多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。同一個結構體可以設置多個鍵值對Tag,不同鍵值之間使用空格分開。
    json同tag的使用如下。
  • 示例13
type User struct {
    Name string
    Age int
    weight uint
}

type User2 struct {
    Name string `json:"姓名"`
    Age int `json:"age_"`
    weight uint `json:"WEI"`
}

func main() {
    u := &User{
        Name: "jack ma",
        Age:  99,
        weight:10,
    }
    fmt.Println(u)
    bytes, _ := json.Marshal(u)
    //{"Name":"jack ma","Age":99} 沒有weight,因爲是小寫代碼開頭,
    //go語言中小寫開頭的字段爲private因此如果需要序列化就得大寫。並且對外暴露
    //可以通過tag實現json的序列化。
    fmt.Printf("%v\n", string(bytes))
    u2 := &User2{
        Name: "jack ma",
        Age:  99,
        weight:10,
    }
    fmt.Println(u)
    //使用tag和json庫配合來進行靈活的序列化轉換
    bytes2, _ := json.Marshal(u2)
    fmt.Printf("%v\n", string(bytes2))
}
/**
&{jack ma 99 10}
{"Name":"jack ma","Age":99}
&{jack ma 99 10}
{"姓名":"jack ma","age_":99}
 */
  1. 通過反射獲取Tag的中key對應的value
  • 示例14
type User3 struct {
    Name string `json:"姓名" pack:"name"`
    Age int `json:"age_"`
    weight uint `json:"WEI"`
}
func main() {
    u := User3{
        Name:   "ttt",
        Age:    0,
        weight: 0,
    }
    v := reflect.ValueOf(u)
    t := v.Type()
    for i, n := 0, t.NumField(); i < n; i++ {
        //打印每個字段的tag和對應的值
        fmt.Printf("%s  i [%v]|[%v] \n", t.Field(i).Tag, t.Field(i).Tag.Get("json"), t.Field(i).Tag.Get("pack"))
    }
}
/**
output:
json:"姓名" pack:"name"   [姓名]|[name]
json:"age_"   [age_]|[]
json:"WEI"   [WEI]|[]
*/

Tag最常用的場景就是json序列化和反序列化。
4. Tag是類型的一部分,Tag不同意味着類型也不同

空結構類型

空接口-struct{},沒有字段域,struct{}的變量或者struct{}爲元素的數組的長度均爲0.

  1. 空結構體的用法
  • 示例15
/*
空結構
 */

func main() {
    var a struct{}
    var b [100]struct{}
    println(unsafe.Sizeof(a))
    println(unsafe.Sizeof(b))
}
/**
output:
0
0
 */

struct{}元素的數組,不會分配類型,但是可以進行切片操作。


func main() {
    var b [100]struct{}
    s := b[:]
    s2 := b[10:]
    fmt.Println(cap(s), len(s), s[8])
    fmt.Println(cap(s2), len(s), s2[8])
    
    fmt.Printf("%p %p %p\n", &b[0], &b[50], &b[99])
}
/**
output:
100 100 {}
90 100 {}
0x596c18 0x596c18 0x596c18
 */

以上可以看出來struct{}類型的元素組成的數組,元素的地址都是同一個,實際上0x596c18 是運行時的runtime.zerobase變量。

  1. 空結構體的使用場景
    空接口主要用於事件通知,經常作爲通道元素來使用。
func main() {
    exitChan := make(chan struct{})
    
    go func() {
        println("go rountine running")
        exitChan <- struct{}{}
    }()
    
    <-exitChan
    println("sub go runtine exit")
}
/**
output:
go rountine running
sub go runtine exit
 */

結構體的對齊

對齊基本和c語言的方式一致。按照4的倍數不足進行填充。
示例後續補充—TODO

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章