第五章(数据)[下]-结构体

  • 结构体(struct)将多个不同类型命名字段序列打包成一个复合类型

  • 字段名唯一,可用"_"补位,字段名排列顺序属于类型组成部分

type node struct {
	_    int
	name string
	age  byte
}

func main() {
	n1 := node{
		name: "wang",
		age:  1,
	}
	fmt.Println(n1) //{0 wang 1}
}
  • 可按顺序初始化全部字段,或使用命名方式初始化指定字段
func main() {
	type user struct {
		name string
		age  byte
	}
	u1 := user{"wang", 13} //按顺序初始化全部字段
	//u2 := user{ "zhang"} //too few values in user{...}  没字段名时要按顺序初始化全部字段
	u2 := user{name: "zhang"} //使用命名方式初始化指定字段
	fmt.Println(u1, u2)       //{wang 13} {zhang 0}
}

推荐使用命名初始化。这样在扩充结构字段或调整字段顺序时,不会导致初始化语句出错。

  • 可直接定义匿名结构类型变量
func main() {
	u := struct {
		name string
		age  byte
	}{
		name: "wang",
		age:  18,
	}
	fmt.Println(u)
}
  • 匿名结构体做字段变量,因其缺少类型标识,在作为字段类型时无法直接初始化
func main() {
	type file struct {
		name string
		attr struct {
			owner int
			perm  int
		}
	}
	f := file{
		name: "test.txt",
		//missing type in composite literal
		//attr: {
		//	owner: 1,
		//	perm:  600,
		//},
	}
        //正确初始化方式
	f.attr.owner = 1
	f.attr.perm = 600
	fmt.Println(f)
}
  • 结构体只有所有的字段全部支持时,才可做相等操作。

错误示例

func main() {
	type data struct {
		x int
		y map[string]int //invalid operation: d1 == d2 (struct containing map[string]int cannot be compared)
	}

	d1 := data{
		x: 100,
	}
	d2 := data{
		x: 100,
	}
	fmt.Println(d1 == d2) //map不支持相等操作
}

正确示例

func main() {
	type data struct {
		x int
		y string
	}

	d1 := data{
		x: 100,
	}
	d2 := data{
		x: 100,
	}
	fmt.Println(d1 == d2) //true
}
  • 可使用指针直接操作结构字段,但不能是多级指针
func main() {
	type data struct {
		x int
		y string
	}
	p := &data{
		x: 1,
		y: "aaa",
	}
	p.x++
	p.y = "bbb"
	p2 := &p
	*p2.y = "ccc" //p2.y undefined (type **data has no field or method y)

}
  • 空结构(struct{})是指没有字段的结构类型
  • 空结构体比较特殊,无论是自身,还是作为数组元素,其长度都为零
func main() {
	var a struct{}
	var b [100]struct{}
	println(unsafe.Sizeof(a), unsafe.Sizeof(b)) // 0 0
}
  • unsafe.Sizeof
    • unsafe.Sizeof返回的是数据类型大小,string在Go中底层类型是一个结构体
    type StringHeader struct {
        Data uintptr
        Len int
    }

在64位系统上,uintptr和int都是8字节

func main() {
	test := "abc"
	a := len(test)
	b := unsafe.Sizeof(test)
	fmt.Println(a, b) //3 16
}
  • 空结构可作为通道元素类型,用于事件通知。
func main() {
	exit := make(chan struct{})

	go func() {
		println("hello world")
		time.Sleep(time.Second * 3)
		exit <- struct{}{}
	}()
	<-exit
	println("end")
}

  • 匿名字段(anonymous field),是指没有名字,仅有类型的字段,也被称作嵌入字段或嵌入类型
func main() {
	type attr struct {
		perm int
	}
	type file struct {
		name string
		attr //仅有类型名-嵌入字段或嵌入类型
	}
	f := file{
		name: "test.txt",
		attr: attr{ //显示初始化匿名字段
			perm: 0755,
		},
	}
	f.perm = 0664   //直接设置匿名字段成员
	println(f.perm) //读取匿名字段

}
  • 嵌入包中的类型,则隐式字段名字不包括报名
type data struct {
	os.File
}

func main() {
	d := data{
		File: os.File{},
	}
	fmt.Printf("%#v\n", d) //%#v:获取数据的值,如果是结构体,会携带结构体名和字段名。
        //main.data{File:os.File{file:(*os.file)(nil)}}
}

  • 不能将基础类型和指针类型同时嵌入,因为两者隐式名字相同
type data struct {
	*int
	int //Duplicate field 'int'
}
  • 除了接口指针多级指针以外的任何命名类型都可以作为匿名字段
func main() {
	type a *int
	type b **int
	type c interface{}
	type d struct {
		*a //embedded type cannot be a pointer
		b  //embedded type cannot be a pointer
		*c //embedded type cannot be a pointer
	}
}
  • 使用匿名字段时会存在重名问题,默认情况下,编译器从当前显式命名字段开始,逐步向内查找匿名字段成员。这时候就必须使用显式字段名
type file struct {
	name string
}
type data struct {
	file
	name string //与匿名字段file.name重名
}

func main() {
	d := data{
		name: "data",
		file: file{"file"},
	}
	d.name = "data2"
	d.file.name = "file2"
	fmt.Println(d.name, d.file.name) //data2 file2
}

  • 多个相同层级的匿名字段重名,就只能使用显式字段访问

  • 字段标签(tag)并不是注释,而是用来对字段进行描述的元数据。标签不属于数据成员,但却是类型的组成部分

  • 在运行期,可用反射获取标签信息。常用做格式校验,数据库关系映射等。

type user struct {
	name string `昵称`
	sex  byte   `性别`
}

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

	}
  • 内存布局
    • 结构类型内存总是一次性分配,各字段在相邻的地址空间按定义顺序排列。
    • 对于引用类型、字符串、指针,结构内存只包含基本(头部)数据
    • 所有匿名字段成员也被包含在内
    • 可借助unsafe包中的相关函数,输出所有字段的偏移量和长度
    • 在分配内存时,字段必须做对齐处理,通常以所在字段中最长的基础类型宽度为标准
    • 比较特殊的空结构类型字段。如果它是最后一个字段,那么编译器将其当作长度为1个字节的类型做对其处理,以便其地址不会越界,避免引发垃圾回收错误
    • 如果仅有一个空结构字段,那么同样按1个字节对齐,长度为0,且指向runtime.zerobase变量
    • 字段对齐是与硬件平台,以及访问效率有关。cpu访问自然对齐的数据所需的读周期最少,还可避免拼接数据。
posted @ 2023-01-11 00:01  巴达克  阅读(18)  评论(0编辑  收藏  举报