第五章(数据)[下]-结构体
-
结构体(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访问自然对齐的数据所需的读周期最少,还可避免拼接数据。