18.2 结构体特性

结构体的内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。

递归结构体

递归结构体类型可以通过引用自身指针来定义。这在定义链表或二叉树的节点时特别有用,此时节点包含指向临近节点的链接。例如:

type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element
	// The list to which this element belongs.
	list *List
	// The value stored with this element.
	Value interface{}
}

可见性

通过参考应用可见性规则,如果结构体名不能导出,可使用 new 函数使用工厂方法的方法达到同样的目的。例如:

type bitmap struct {
	Size int
	data []byte
}
func NewBitmap(size int) *bitmap {
	div, mod := size/8, size%8
	if mod > 0 {
		div++
	}
	return &bitmap{size, make([]byte, div)}
}

在包外,只有通过NewBitmap函数才可以初始bitmap结构体。同理,在bitmap结构体中,由于其字段data是小写字母开头即并未导出,bitmap结构体的变量不能直接通过选择器读取data字段的数据。

带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag)。它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有 reflect 包能获取它。

reflect包可以在运行时反射得到类型、属性和方法。如变量是结构体类型,可以通过 Field() 方法来索引结构体的字段,然后就可以得到Tag 属性。例如:

package main
import (
	"fmt"
	"reflect"
)
type Student struct {
	name string "学生名字"          // 结构体标签
	Age  int    "学生年龄"          // 结构体标签
	Room int    `json:"Roomid"` // 结构体标签
}
func main() {
	st := Student{"Titan", 14, 102}
	fmt.Println(reflect.TypeOf(st).Field(0).Tag)
	fmt.Println(reflect.TypeOf(st).Field(1).Tag)
	fmt.Println(reflect.TypeOf(st).Field(2).Tag)
}
程序输出:
学生名字
学生年龄
json:"Roomid"

从上面代码中可以看到,通过reflect我们很容易得到结构体字段的标签。