go——结构

Go语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

结构体定义需要使用type和struct语句。struct语句定义一个新的数据类型,结构体中有一个或多个成员。
type语句设定了结构体的名称。格式如下:

1
2
3
4
type struct_name  struct {
    name string
    talk Talk
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

1
variable_name := structure_name{value1,value2}  //顺序必须与结构体的定义一致

或者:

1
variable_name := structure_name{key1:value1,key2:value2}  //结构体名称:值

 

结构体类型中的每个字段都需要独占一行。一般情况下,字段声明需由字段名称和表示字段类型的字面量组成。
还有一种只有类型字面量的无名称字段,称为嵌入字段。
虽然嵌入字段可以用来无缝集成额外字段和方法,但是其嵌入规则和使用规则都比较复杂。

结构体类型的值一般由复合字面量来表达。
复合字面量可以由类型字面量和花括号包裹的键值对列表组成。
这里,键就是结构体类型中某个字段的名称,而值(又称元素)就是要赋给该字段的那个值。
表示结构体值的复合字面量可以简称为结构体字面量。
在同一个结构体字面量中,一个字段名称只能出现一次。

字段名必须唯一,可用"_"补位,支持使用自身指针类型成员.
字段名,排列顺序属于类型组成部分.除对齐处理外,编译器不会优化和调整内存布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import "fmt"
 
type node struct {
    _    int //没给值会使用默认值
    id   int
    next *node
}
 
func main() {
    n1 := node{
        id: 1,
    }
 
    n2 := node{
        id:   2,
        next: &n1,
    }
 
    fmt.Println(n1, n2) //{0 1 <nil>} {0 2 0xc000048400}
}

 

可按顺序初始化全部字段,或使用命名方式初始化指定字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import "fmt"
 
func main() {
    type user struct {
        name string
        age  int
    }
 
    u1 := user{"Tom", 12} //如果顺序初始化字段,就必须赋值全部字段
    u2 := user{"Kebi"}    //too few values in user literal,字段数量不够
    u3 := user{           //命名初始化
        name: "maoixan",
        age:  18,
    }
 
    fmt.Println(u1, u3)
}

 

推荐使用命名初始化,这样在扩充结构字段或调整字段顺序时,不会导致语句初始化错误.
可以直接匿名结构类型变量,还可以将结构体用作字段类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
 
import "fmt"
 
func main() {
    u := struct { //直接定义匿名结构变量
        name string
        age  int8
    }{
        name: "kebi",
        age:  18,
    }
 
    type file struct {
        name string
        attr struct { //定义匿名结构类型字段
            owner int
            perm  int
        }
    }
 
    f := file{
        name: "test.py",
        // attr: {  //missing type in composite literal,对于结构体中的结构体赋值方式有所不同
        // owner: 10,
        // perm:  755,
        // },
    }
    f.attr.owner = 10 //正确方式
    f.attr.perm = 755
 
    fmt.Println(u, f) //{kebi 18} {test.py {10 755}}
}

 

只有在所有字段类型全部支持时,才可做相等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import "fmt"
 
func main() {
    type data struct {
        x int
        y map[string]int //字典类型不支持==,
    }
 
    d1 := data{
        x: 100,
    }
 
    d2 := data{
        x: 100,
    }
    fmt.Println(d1 == d2) //struct containing map[string]int cannot be compared
}

 

可使用指针直接操作结构字段,但不能是多级指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import "fmt"
 
func main() {
    type user struct {
        name string
        age  int
    }
 
    p := &user{ //获取指针
        name: "kebi",
        age:  26,
    }
 
    p.name = "maoxian" //通过指针找到对应的程序实体
    p.age++
    fmt.Println(p) //&{maoxian 27}
 
    p2 := &p              //&p属于二级指针
    *p2.name = "xiaoniao" //p2.name undefined (type **user has no field or method name)
}

 

空结构

空结构struct{}是指没有字段的结构类型。
它比较特殊,因为无论是其自身,还是作为数组元素类型,其长度都为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
 
import (
    "fmt"
    "unsafe"
)
 
func main() {
    var a struct{}      //匿名结构体
    var b [100]struct{} //以结构体作为元素类型的数组
 
    fmt.Println(unsafe.Sizeof(a), unsafe.Sizeof(b)) //0 0
 
    s := b[:]
    b[1] = struct{}{} //重新赋值
    s[2] = struct{}{}
    fmt.Println(s[3], len(s), cap(s)) //{} 100 100
}

 

实际上,这类长度为0的对象通常指向runtime.zerobase变量。

1
2
3
4
5
6
7
8
9
10
11
package main
 
import "fmt"
 
func main() {
    a := [10]struct{}{}
    b := a[:]
    c := [0]int{}
 
    fmt.Printf("%p, %p, %p\n", &a[0], &b[0], &c) //0x5771c8, 0x5771c8, 0x5771c8
}

 

空结构可作为通道元素类型,用于事件通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
 
import "fmt"
 
func main() {
    exit := make(chan struct{})
 
    go func() {
        fmt.Println("hello, world!")
        exit <- struct{}{}
    }()
 
    <-exit
    fmt.Println("end.")
}

 

匿名字段
所谓匿名字段是指没有名字,仅有类型的字段,也称作嵌入字段或嵌入类型。
从编译器角度看,这只是隐式地以类型名作为字段名称。
可直接引用匿名字段的成员,但初始化时必须当作独立字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
 
import "fmt"
 
type attr struct {
    perm int
}
 
type file struct {
    name string
    attr //仅有类型名
}
 
func main() {
    f := file{
        name: "test.dat",
        attr: attr{ //将类型名当作字段名
            perm: 755,
        },
    }
    f.perm = 500           //直接设置匿名字段成员
    fmt.Println(f, f.perm) //直接读取匿名字段成员
}

 

如果嵌入其它包中的类型,则隐式字段名称不包括包名。
不仅仅是结构体,除接口指针和多级指针以外的任何命名类型都可以作为匿名字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
 
import "fmt"
 
type data struct {
    *int   //嵌入指针类型
    string
}
 
func main() {
    x := 100
    d := data{
        int:    &x,  //使用基础类型作为字段名
        string: "abc",
    }
 
    fmt.Printf("%#v\n", d)
}
/*
main.data{
    int:(*int)(0xc00000a168),
    string:"abc"
}
*/
 
不能将基础类型和其指针类型同时嵌入,因为两者隐式名字相同,下面就是错误示例。
// type data struct {
    // *int
    // int
// }

 

虽然可以像普通字段那样访问匿名字段成员,但会存在重名问题。
默认情况下,编译器从当前显式命名开始,逐步向内查找匿名字段成员。
如果匿名字段成员被外层同名字段遮蔽,那么必须使用显式字段名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
 
import "fmt"
 
type file struct {
    name string
}
 
type data struct {
    file
    name string //与匿名字段file.name重名
}
 
func main() {
    d := data{
        name: "data",
        file: file{"file"}, //这种方式赋值并没有影响
    }
 
    fmt.Println(d.name, d.file.name) //data file
    d.name = "data2"
    d.file.name = "file2"
 
    fmt.Println(d.name, d.file.name) //data2 file2
}

 

如果多个相同层级的匿名字段成员重名,就只能使用显式字段名访问,因为编译器无法确定目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main
 
import "fmt"
 
type file struct {
    name string
}
 
type log struct {
    name string
}
 
type data struct {
    file //file和log层次相同
    log  //file.name和log.name重名
}
 
func main() {
    d := data{
        file: file{"1.txt"},
        log:  log{"test.log"},
    }
    fmt.Println(d) //{{1.txt} {test.log}}
 
    d2 := data{}
    // d2.name = "name"      //ambiguous selector d2.name
    d2.file.name = "file" //显式命名字段
    d2.log.name = "log"
    fmt.Println(d2) //{{file} {log}}
}

 

严格说来,Go并不是传统意义上的面向对象编程语言,或者说仅实现了最小面向对象的机制。
匿名嵌入不是继承,无法实现多态处理。
虽然配合方法集,可用接口来显现一些类似的操作,但其本质完全不同。


字段标签

字段标签(tag)并不是注释,而是用来对字段进行描述的元数据。
尽管它不属于数据成员,但却是类型的组成部分。
在运行期,可以反射获取标签信息。常被用作格式校验,数据库关系映射等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
 
import (
    "fmt"
    "reflect"
)
 
type user struct {
    name string `昵称`
    sex  int    `性别`
}
 
func main() {
    u := user{"Tom", 1}
    v := reflect.ValueOf(u)
    t := v.Type()
 
    for i, n := 0, t.NumField(); i < n; i++ {
        fmt.Printf("%s: %v\n", t.Field(i).Tag, v.Field(i))
    }
}
 
/*
昵称: Tom
性别: 1
*/

  

 

posted @   明王不动心  阅读(694)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示