为什么在C中嵌套在结构体中需要声明联合类型的变量?

Why do we need to declare a variable of union type when nested in a structure in C?

我有教程中的代码示例,上面写着

struct goods {
    char name[20];
    union quantity {
        int count;
        float weight, volume;
    } q;
};

我不明白为什么我们需要声明 'q' 变量和联合类型名称 'quantity'?为什么我们不能只使用 'quantity' 然后通过点访问结构字段?


更新:'quantity' 是类型联合的 name/tag 是否正确,而 'q' 不是变量而是联合的名称 member/field其中包含子成员(计数、重量、体积)?

I cant figure out why do we need to declare 'q' variable along with a union type name 'quantity'?

如问题中所述,struct goods 是一种具有两个成员的结构类型:由 name 标识的 20 char 数组和由 [ 标识的 union quantity =16=](是的,quantity 是联合标记,而不是成员的名称)。在任何绝对意义上都没有必要以这种方式声明它,但这样的声明提供了一些其他选择所没有的特征。但是,请务必理解,如示例中所声明的,countweightvolume 而非 struct goods 成员。相反,他们是 q 的成员,该联盟是 struct goods.

的成员

Why can't we get away with just 'quantity' and then access struct fields via dot?

因为这不是 C 语法提供的替代方案之一。在结构类型声明的成员列表中,联合标记(在本例中为 quantity)只能出现在 named 成员的声明中,因此如果提供了则您还必须声明联合的标识符——示例中的 q。并且将联合声明为命名成员后,您必须通过联合的标识符访问 成员。

另一方面,您可以省略标签,如果您这样做,那么您也可以选择省略联合的标识符。如果您确实省略了标识符(并且仅在这种情况下),您将拥有一个 "anonymous union member" 其自己的成员被访问 就好像 它们实际上是包含结构的成员一样。这与您的要求非常接近。

请注意,无论如何,联合体的成员彼此共享存储空间,因此联合体在任何给定时间都只包含其中一个成员。它们不与包含结构的其他成员共享存储空间。

话虽如此,各种选项在其特性上确实存在一些差异。首先,请注意所有这些形式都具有双重意义:它们声明联合 type,并且声明该类型的结构成员。这是相关的,因为如果您提供一个标记,那么您可以在联合声明范围内的任何地方声明相同联合类型的其他对象。此外,该范围不限于包含它的结构类型声明,因此根据所提供的声明,可以执行如下操作:

void set_quantity(struct goods *g, union quantity quant) {
    g->q = quant;
}

对于未标记的联合来说这是不可能的。

具有未标记联合类型的命名成员与匿名联合成员之间至少还有一个重要区别:只有命名了联合才能访问它本身。考虑一下:

struct goods2 {
    char name[20];
    union {
        int count;
        float weight, volume;
    } q;
};

void copy_quantity(struct goods2 *dest, struct goods2 *src) {
    dest->q = src->q;
}

你不仅不能对一个匿名的工会成员这样做,你也不能做任何可靠的等效事情。特别是,即使您愿意忍受与单独复制 src->countsrc->weightsrc->volume 相关的低效率,尽管其中只有一个实际包含一个值,C 也不提供承诺以任何顺序这样做都会可靠地达到预期的结果。

不清楚你问的关于这段代码的具体问题是什么,所以让我们回顾一下这些问题。

结构成员

union 声明出现在 struct 声明中时,它通常声明该结构的一个联合成员。该成员是结构的一部分,与任何其他成员一样,例如结构中声明的 int x。结构的每个实例都包含其每个成员的实例,包括联合——联合是结构的一部分,而不是单独的东西。

名字

在此代码中:

    union quantity {
        int count;
        float weight, volume;
    } q;

标识符 quantity 是联盟的 标签 。在此角色中,它必须出现在 union 关键字之后,始终显示为 union quantity。它只命名 union type;它不命名结构的任何联合 objectmember。 (同一个标识符可以在多个角色中使用。我们可以 添加一个声明,将 quantity 定义为类型或对象或成员,然后它将有两个角色:它可以用作 union quantity 来指代联合类型,它可以单独使用来指代其他声明声明的任何内容。)

在上面的相同代码中,q 是结构成员的名称。它是 struct goods.

的每个实例中的 union quantity 对象的名称

通过这个声明,如果我们定义一个struct goods G;,那么G.q指的是G中的union quantity,而G.q.countG.q.weightG.q.volume 指的是联盟 G.q 中的成员。 (一次只能存储其中一个成员,因为它们都重叠在一个联合中。)

匿名联盟

在 C 2011 中,添加了一项新功能。 unionstructure 可以在没有成员名称的情况下在另一个 unionstructure 中声明:

struct goods {
    char name[20];
    union {
        int count;
        float weight, volume;
    };
};

这根本不会改变结构的布局——它仍然具有相同的成员。但是,它们的名称不同。给定 struct goods G,我们可以将 count 成员称为 G.count 而不是 G.q.count,对于 weightvolume 也是如此。 (请注意,除了删除成员名称 q 之外,此代码还删除了标记 quantity。C 标准中有一条规则说对于匿名的结构或联合,它不能有标签,也不能有成员名称。我看不出这有什么技术原因。也许是为了避免成员名称被无意遗漏的错误。)

至于为什么有人会给联合成员取个名字而不是匿名,原因之一是代码是在 2011 年之前编写的,或者是在 2011 年之后编写的,但用于尚不支持匿名成员的 C 实现中.另一个原因是他们想区分联合成员,这样任何阅读或编写代码的人都会警惕这些成员在结构内部的某个地方,而不是常规的直接结构成员。