我正在使用go,特别是QT绑定。但是我不明白在下面的结构中使用前导下划线。我知道一般使用下划线但不是这个具体的例子。
type CustomLabel struct {
core.QObject
_ func() `constructor:"init"`
_ string `property:"text"`
}
它与struct标签有关吗?
答案 0 :(得分:8)
您可以将其视为该类型的元信息,但无法通过该类型的实例访问,但可以使用reflect
或go/ast
进行访问。这为感兴趣的软件包/程序提供了一些关于如何处理该类型的指令。例如,基于这些标签,它可以使用go:generate。
考虑到其中一个标记显示constructor:"init"
且字段的类型为func()
,很可能将其与go:generate一起使用以生成名为init
的构造函数或初始化方法对于CustomLabel
类型。
以下是使用reflect
获取“元”信息的示例(尽管我已经提到过,具体的qt示例可能意味着由go:generate处理)。
type CustomLabel struct {
_ func() `constructor:"init"`
_ string `property:"text"`
}
fmt.Println(reflect.ValueOf(CustomLabel{}).Type().Field(0).Tag)
// constructor:"init"
fmt.Println(reflect.ValueOf(CustomLabel{}).Type().Field(0).Type)
// func()
答案 1 :(得分:6)
这些称为空白字段,因为blank标识符用作字段名称。
它们不能被引用(就像任何具有空白标识符作为其名称的变量一样)但它们参与结构的内存布局。通常并且实际上它们用作填充,以将后续字段与字节位置(或存储器位置)对齐,所述字节位置(或存储器位置)匹配来自(或去往)另一系统的数据的布局。增益是因为这些结构值(或者更确切地说是它们的存储空间)可以在一个步骤中简单有效地转储或读取。
@ mkopriva的回答详细说明了问题的具体用例。
警告:这些空白字段作为“type-annotations”应该谨慎使用,因为它们会增加此类结构的所有(!)值的不必要开销。这些字段不能被引用,但它们仍然需要内存。如果添加一个大小为8字节的空白字段(例如int64
),如果创建一百万个元素,那么这8个字节将计数一百万次。因此,这是空白字段的“有缺陷”使用:目的是将元信息添加到类型本身(而不是其实例),但成本是所有元素都需要增加内存
您可能会说使用大小为0的类型,例如struct{}
。它更好,就像在正确的位置使用一样(例如,作为第一个字段,推理见Struct has different size if the field order is different;还有Why position of `[0]byte` in the struct matters?),它们不会改变结构的大小。尽管如此,使用反射迭代结构字段的代码仍然必须循环遍历这些代码,因此它会使这些代码效率降低(通常是所有编组/解组过程)。此外,由于现在我们不能使用任意类型,我们失去了携带类型信息的优势。
最后一句话(关于何时使用struct{}
我们丢失了携带类型信息)可以被规避。 struct{}
不是唯一具有0大小的类型,所有具有0长度的数组也具有零大小(无论实际的元素类型如何)。因此,我们可以使用我们想要合并的0大小的数组来保留类型信息,例如:
type CustomLabel struct {
_ [0]func() `constructor:"init"`
_ [0]string `property:"text"`
}
现在这个CustomLabel
类型在性能方面看起来比所讨论的类型要好得多:它的大小仍为0.并且仍然可以使用Type.Elem()
来访问数组的元素类型,如本示例所示:
type CustomLabel struct {
_ [0]func() `constructor:"init"`
_ [0]string `property:"text"`
}
func main() {
f := reflect.ValueOf(CustomLabel{}).Type().Field(0)
fmt.Println(f.Tag)
fmt.Println(f.Type)
fmt.Println(f.Type.Elem())
}
输出(在Go Playground上尝试):
constructor:"init"
[0]func()
func()