函数名前带下划线的Go结构标记

函数名前带下划线的Go结构标记,go,struct,naming-conventions,Go,Struct,Naming Conventions,我正在使用go,特别是QT绑定。但是,我不理解在下面的结构中使用前导下划线。我知道一般使用下划线,但不知道这个具体的例子 type CustomLabel struct { core.QObject _ func() `constructor:"init"` _ string `property:"text"` } 它是否与结构标记相关?您可以将其视为该类型的元信息,它不能通过该类型的实例访问,但可以使用reflect或go/ast访问。这为感兴趣的包/程序提供了一些

我正在使用go,特别是QT绑定。但是,我不理解在下面的结构中使用前导下划线。我知道一般使用下划线,但不知道这个具体的例子

type CustomLabel struct {
    core.QObject

    _ func() `constructor:"init"`
    _ string `property:"text"`
}

它是否与结构标记相关?

您可以将其视为该类型的元信息,它不能通过该类型的实例访问,但可以使用
reflect
go/ast
访问。这为感兴趣的包/程序提供了一些关于如何处理该类型的指令。例如,基于这些标记,它可以使用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()
考虑到其中一个标记表示
constructor:“init”
,并且字段的类型是
func()
,很可能这与go:generate一起使用,为类型
CustomLabel
生成名为
init
的构造函数或初始值设定项方法


下面是一个使用
reflect
获取“meta”信息的示例(尽管我已经提到过,具体的qt示例可能是由go:generate处理的)


这些被称为空白字段,因为标识符用作字段名

它们不能被引用(就像任何具有空白标识符的变量一样),但是它们参与了结构的内存布局。通常和实际情况下,它们被用作填充,以将后续字段和字节位置(或内存位置)对齐,这些字节位置和来自(或前往)另一个系统的数据布局相匹配。这样做的好处是,这些结构值(或者更确切地说,它们的内存空间)可以在一个步骤中简单高效地转储或读取

@mkopriva的回答详细说明了问题中的特定用例的用途

警告一句:这些空白字段作为“类型注释”应该谨慎使用,因为它们会给此类结构的所有(!)值增加不必要的开销。无法引用这些字段,但它们仍然需要内存。如果添加一个大小为8字节的空白字段(例如,
int64
),如果创建一百万个元素,那么这8个字节将计数一百万次。因此,使用空白字段是“有缺陷的”:目的是向类型本身(而不是其实例)添加元信息,但代价是所有元素都需要增加内存

然后,您可能会说使用大小为0的类型,例如
struct{}
。更好的是,如果在正确的位置使用(例如,作为第一个字段,用于推理,请参见,还有),它们不会更改结构的大小。尽管如此,使用反射来迭代结构字段的代码仍然必须在这些字段上循环,因此这会降低此类代码的效率(通常是所有封送/解封过程)。而且,由于现在我们不能使用任意类型,我们失去了携带类型信息的优势

最后一条语句(关于当使用
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())
}
输出(在上尝试):


有关struct标记的概述,请阅读相关问题:

为什么要使用不同的类型?它们可能都不是空白字符串,或者您只能有一个空白标识符?@AyubMalik这两个空白字段有两个不同的用途。第一个包含应该生成
init()
构造函数(不带参数)的信息。第二个字段包含应生成类型为
string
text
属性的信息。@icza零大小空白字段的另一个选项是定义描述意图的空结构类型。例如,
type Func struct{}
,然后字段看起来像
\ufunc`constructor:“init”`
@mkopriva不幸的是,该解决方案不会为类型提供反射,因此基本上该解决方案比在标记本身中提供类型弱,如
`property:“text”type:“string”`
@mkopriva因为您有名称
“Func”
,但这本身并不能识别类型
Func()
,这需要在生成器中内置一个额外的类型注册表,或者在源代码中创建一个单独的表。字符串
“Func”
无法神奇地转换为标识
Func()
类型的
reflect.Type
constructor:"init"
[0]func()
func()