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

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