结构和联合

结构 包含数据对象的有序组。 与数组的元素不同,结构中的数据对象可以具有不同的数据类型。 结构中的每个数据对象都是 成员字段

union 是一个类似于结构的对象,只是它的所有成员都在内存中的同一位置开始。 联合变量一次只能表示其一个成员的值。

仅限 C + + 开始在C++中,结构和联合体与类相同,只是它们的成员和继承在默认情况下是公开的。仅限 C++ 结束

您可以在该类型的变量定义之外单独声明结构或并集类型,如 结构和并集类型定义结构和并集变量声明中所述; 也可以在一个语句中定义结构或并集数据类型以及具有该类型的所有变量,如 结构和并集类型以及单个语句中的变量定义中所述。

结构和结合都受到调整考虑因素的制约。 有关对齐的完整讨论,请参阅 "对齐数据"

结构和并集类型定义

结构或并集 类型定义 包含 structunion 关键字,后跟可选标识 (结构标记) 和用花括号括起的成员列表。

结构或并集类型定义语法

读取语法图跳过可视语法图structunion 标记标识 { 成员声明; } ;

tag_identifier 为类型提供了名称。 如果未提供标记名称,那么必须将所有引用该类型的类型的变量定义放在该类型的声明中,如 结构和并集类型以及单个语句中的变量定义中所述。 同样,不能将类型限定符与结构或并集定义一起使用; 放在 structunion 关键字前面的类型限定符只能应用于在类型定义中声明的变量。

成员声明

成员列表提供了结构或并集数据类型以及可存储在结构或并集中的值的描述。 成员的定义具有标准变量声明的形式。 成员变量的名称在单个结构或并集中必须是不同的,但在同一作用域内定义的另一个结构或并集类型中可以使用相同的成员名称,甚至可以与变量,函数或类型名称相同。

结构或联合成员可以是任何类型,但以下类型除外:
  • 任何可变修改类型
  • void 类型
  • C 功能
  • 任何不完整类型
由于不允许不完整的类型作为成员,因此结构或并集类型可能不包含自身作为成员的实例,但允许包含指向自身实例的指针。 作为一种特殊情况,具有多个成员的结构的最后一个成员可能具有不完整的数组类型,称为 弹性数组成员,如 弹性数组成员中所述。
IBM 扩展开始
作为 标准C和C++ 的扩展,与 GNU C/C++ 兼容, XL C/C++ 还允许零范围数组作为结构和联合体的成员,如零范围数组成员( IBM 扩展) 中所述。IBM 扩展结束

仅限 C + + 开始类对象不能是具有构造函数、析构函数或重载复制赋值运算符的类成员,也不能是引用类型。 工会会员不能以 static 作为关键词进行搜索。 仅限 C++ 结束

不代表位字段的成员可以使用类型限定符 volatileconst进行限定。 结果是 lvalue。

结构成员按递增顺序分配给内存地址,第一个组件从结构名称本身的起始地址开始。 为了允许组件正确对齐,可在结构布局中的任何连续成员之间显示填充字节。

分配给联合的存储器是联合的最大成员所需的存储器 (加上需要的任何填充,以便联合将在其具有最严格要求的成员的自然边界处结束)。 所有并集的组件都有效覆盖在内存中: 并集的每个成员都从并集开始时开始分配存储器,并且一次只能有一个成员占用存储器。

灵活数组成员

柔性阵列成员在结构中出现的无界阵列。 这是一个 C99 功能, 仅限 C + + XL C/C++ 编译器支持它作为 IBM 扩展仅限 C + +。 可使用灵活的数组成员来访问可变长度对象。 允许柔性阵列成员作为结构的最后一个成员,前提是该结构具有多个指定成员。 它使用空索引进行声明,如下所示:

array_identifier [];

例如, b 是结构 f的灵活数组成员。
struct f{
  int a;
  int b[];
};
由于弹性阵列成员的类型不完整,因此无法将 sizeof 运算符应用于弹性阵列在此示例中,语句 sizeof(f) 返回与 sizeof(f.a)相同的结果,即整数的大小。 无法使用语句 sizeof(f.b) ,因为 b 是类型不完整的灵活数组成员。
任何包含灵活数组成员的结构都不能是另一个结构的成员或数组的元素,例如:
struct f{
  int a;
  int b[];
};
struct f fa[10]; // Error.
IBM 扩展为了与GNU C/C++ 兼容, XL C/C++ 编译器扩展了标准C 和C++ ,以放宽对灵活数组成员的约束,并允许以下情况:
  • 可以在结构的任何部分中声明柔性阵列成员,而不仅仅是最后一个成员。 跟在柔性阵列成员后面的任何成员的类型都不需要与柔性阵列成员的类型兼容; 但是,当柔性阵列成员跟在不兼容类型的成员后面时,会发出警告消息。 以下示例对此进行了演示:
    struct s {
      int a;
      int b[];
      char c; // The compiler issues a warning message.
    } f;
  • 包含柔性阵列成员的结构可以是其他结构的成员。
  • C 仅开始只有满足以下两个条件之一时,才能对弹性阵列成员进行静态初始化:
    • 柔性阵列成员是结构的最后一个成员,例如:
      struct f { 
        int a;
        int b[];
      } f1 = {1,{1,2,3}}; // Fine.
      
      struct a {    
        int b;
        int c[];
        int d[];
      } e = { 1,{1,2},3}; // Error, c is not the last member
                          // of structure a.
      
    • 柔性数组成员包含在嵌套结构的最外层结构中。 内部结构的成员不能以静态方式初始化,例如:
      struct b {
        int c;
        int d[]; 
      }; 
      
      struct c { 
        struct b f;
        int g[]; 
      } h ={{1,{1,2}},{1,2}}; // Error, member d of structure b is
                              // in the inner nested structure.
      
    仅限 C 结束

结束 IBM 扩展

零扩展数据块阵列成员 (IBM 扩展)

零扩展数据块数组是为了实现 GNU C/C++ 兼容性而提供的,可用于访问变长对象。

零扩展数据块数组是指定为其维的显式零的数组。
array_identifier [0]
例如, b 是结构 f的零扩展数据块数组成员。
struct f{
  int a;
  int b[0];
};
sizeof 运算符可以应用于零扩展数据块数组,返回的值为 0。 在此示例中,语句 sizeof(f) 返回与 sizeof(f.a)相同的结果,后者是整数的大小。 语句 sizeof(f.b) 返回 0。
包含零扩展数据块数组的结构可以是数组的元素,例如:
struct f{
  int a;
  int b[0];
};
struct f fa[10]; // Fine.
零扩展数据块数组只能使用空集 {}静态初始化。 否则,必须将其初始化为动态分配的阵列。 例如:
struct f{
  int a;
  int b[0];
};
struct f f1 = {100, {}}; //Fine.
struct f f2 = {100, {1, 2}}; //Error.
如果未初始化零扩展数据块数组,那么不会进行静态零填充,因为零扩展数据块数组定义为没有成员。 以下示例对此进行了演示:
#include <stdio.h>

struct s {
  int a;
  int b[0];
};

struct t1 {
  struct s f;
  int c[3];
} g1 = {{1},{1,2}};

struct t2 {
  struct s f;
  int c[3];
} g2 = {{1,{}},{1,2}};

int main() {
  printf("%d %d %d %d\n", g1.f.a, g1.f.b[0], g1.f.b[1], g1.f.b[2]);
  printf("%d %d %d %d\n", g2.f.a, g2.f.b[0], g2.f.b[1], g2.f.b[2]);
  return 0;
} 
在此示例中,两个 printf 语句生成相同的输出:
1 1 2 0
可以在结构的任何部分中声明零扩展数据块数组,而不仅仅是最后一个成员。 零扩展数据块阵列之后的任何成员的类型都不需要与零扩展数据块阵列的类型兼容; 但是, 当零扩展数据块阵列后跟不兼容类型的成员时, 会发出警告。 例如:
struct s {
  int a;
  int b[0];
  char c;    // Issues a warning message
} f;
只能将零扩展数据块数组声明为聚集类型的成员。 例如:
int func(){
  int a[0];        // error
  struct S{
    int x;
    char b[0];     // fine
  };
}
位字段成员

C 和 C++ 都允许将整数成员存储到比编译器通常允许的更小的内存空间中。 这些节省空间的结构成员称为 位字段,它们的宽度 (以位计) 可以显式声明。 位字段用于必须强制数据结构与固定硬件表示相对应且不可移植的程序中。

位字段成员声明语法

读取语法图跳过可视语法图 type_说明符 声明符 : constant_expression ;

constant_expression 是一个常量整数表达式,用于指示字段宽度 (以位计)。 位字段声明不能使用类型限定符 constvolatile

C 仅开始
在 C99中,位字段的允许数据类型包括 _Boolintsigned intunsigned intIBM 扩展开始位域的数据类型也可以是任何版本的 charshortlong。 如果某个位字段的类型为 charshort的任何版本,那么其类型将由 signed intunsigned int替换,具体取决于 -qbitfields的设置。IBM 扩展结束 如果位字段是纯文本 或纯文本 ,则位字段是 还是 取决于 的设置。 int long signed unsigned -qbitfields
仅限 C 结束

仅限 C + + 开始位域可以是任何整数类型或枚举类型。 如果位域是纯文本 short、纯文本 int 或纯文本 long ,则位域是 signed 还是 unsigned 取决于 -qbitfields 的设置。仅限 C++ 结束

最大位字段长度为 64 位。 要提高可移植性,请不要使用大于 32 位大小的位字段。

以下结构具有三个位字段成员 kingdom, phylumgenus,分别占 12 位, 6 位和 2 位:
struct taxonomy {
  int kingdom : 12;
  int phylum : 6;
  int genus : 2;
};

当您将超出范围的值分配给位字段时,将保留低阶位模式并分配相应的位。

以下限制适用于位字段。 您不能:
  • 定义位字段数组
  • 采用位字段的地址
  • 有一个指向位字段的指针
  • 具有对位字段的引用
位字段是位打包的。 它们可以跨越字和字节边界。 在两个 (非零长度) 位字段成员之间未插入任何填充。 如果下一个成员是长度为零的位字段或非位字段,那么可以在位字段成员之后进行位填充。 非位字段成员根据其声明的类型进行对齐。 例如,以下结构演示了位字段成员之间缺少填充,以及在非位字段成员之前的位字段成员之后插入填充。
struct {
  int larry : 25;	// Bit Field: offset 0 bytes and 0 bits.
  int curly : 25;	// Bit Field: offset 3 bytes and 1 bit (25 bits).
  int moe;        // non-Bit Field: offset 8 bytes and 0 bits (64 bits).
} stooges;
larrycurly之间没有填充。 curly 的位偏移量将为 25 位。 成员 moe 将在下一个 4 字节边界上对齐,从而在 curlymoe之间产生 14 位填充。

长度为 0 的位字段必须未命名。 无法引用或初始化未命名的位字段。

以下示例演示了填充,并且对所有实现都有效。 假设 int 占用 4 个字节。 此示例声明标识 kitchen 的类型为 struct on_off:
struct on_off {
  unsigned light : 1;
  unsigned toaster : 1;
  int count; /* 4 bytes */
  unsigned ac : 4;
  unsigned : 4;
  unsigned clock : 1;
  unsigned : 0;
  unsigned flag : 1;
} kitchen;
结构 kitchen 包含 8 个成员,总计 16 个字节。 下表描述了每个成员占用的存储器:
成员名 存储器已占用
light 1 位
toaster 1 位
(padding-30 位) 到下一个 int 边界
count int 的大小 (4 字节)
ac 4 位
(未命名字段) 4 位
clock 1 位
(padding-23 位) 到下一个 int 边界 (未命名字段)
flag 1 位
(padding-31 位) 到下一个 int 边界

结构和联合变量声明

结构或并集 声明 具有与定义相同的格式,但声明没有括在花括号内的成员列表。 必须先声明结构或并集数据类型,然后才能定义具有该类型的变量。

结构或并集变量声明语法

读取语法图跳过可视语法图storage_class_说明符类型限定符structunion 标记标识 声明符;

tag_identifier 指示结构或并集的 数据类型

仅限 C + +关键字 struct 在结构变量声明中是可选的。仅限 C++ 结束

您可以声明具有任何存储类的结构或并集。 变量的存储类说明符和任何类型限定符必须出现在语句的开头。 使用 register 存储类说明符声明的结构或并集将被视为自动变量。

以下示例定义了结构类型 address:
struct address {
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
     
以下示例声明两个类型为 address的结构变量:
struct address perm_address;
struct address temp_address;      

单个语句中的结构和并集类型以及变量定义

可以在一个语句中定义结构 (或并集) 类型和结构 (或并集) 变量,方法是将声明符和可选初始化方法放在变量定义之后。 以下示例定义并集数据类型 (未命名) 和并集变量 (名为 length):
union {
  float meters;
  double centimeters;
  long inches;
} length;

请注意,由于此示例未命名数据类型,因此 length 是唯一可以具有此数据类型的变量。 将标识放在 structunion 关键字之后可提供数据类型的名称,并允许您稍后在程序中声明此数据类型的其他变量。

要指定一个或多个变量的存储类说明符,必须将存储类说明符放在语句的开头。 例如:
static struct {
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
} perm_address, temp_address;
在这种情况下,将同时为 perm_addresstemp_address 分配静态存储器。
类型限定符可以应用于类型定义中声明的一个或多个变量。 以下两个示例都有效:
volatile struct class1 {
  char descript[20];
  long code;
  short complete;
} file1, file2;
struct class1 {
  char descript[20];
  long code;
  short complete;
} volatile file1, file2;

在这两种情况下,结构 file1file2 都限定为 volatile

访问结构和联合成员

声明结构或并集变量后,将通过使用点运算符 (.) 指定变量名或使用箭头运算符 (->) 和成员名指定指针来引用成员。 例如,以下两项:
perm_address.prov = "Ontario";
p_perm_address -> prov = "Ontario";
将字符串 "Ontario" 分配给结构 perm_address中的指针 prov

所有对结构和联合成员 (包括位字段) 的引用都必须是完全限定的。 在上一个示例中,第四个字段不能单独由 prov 引用,而只能由 perm_address.prov引用。

C11

匿名结构

匿名结构 是没有标记或名称且是另一个结构或并集的成员的结构。 匿名结构的所有成员的行为就像它们是父结构的成员一样。 匿名结构必须满足以下条件:
  • 该结构嵌套在另一个结构或并集内。
  • 该结构没有标记。
  • 结构没有名称。
例如,以下代码片段演示匿名结构必须满足的条件。
struct v {
  union {
     // This is an anonymous structure, because it has no tag, no name,
     // and is a member of another structure or union.
     struct { int i, j; };

     // This is not an anonymous structure, because it has a name.
     struct { long k, l; } w; 

    // This is not an anonymous structure, because
    // the structure has a tag "phone".
    struct phone {int number, areanumber;};  
  };
  
  int m;
} v1;

匿名工会

匿名联合 是没有标记或名称的联合,并且是另一个联合或结构的成员。 它不能后跟声明程序。 匿名联合不是一种类型; 它定义未命名的对象。

匿名联合的成员名必须与声明联合的作用域内的其他名称不同。 您可以直接在并集作用域中使用成员名,而无需任何其他成员访问语法。

例如,在以下代码片段中,您可以直接访问数据成员 icptr ,因为它们位于包含匿名联合的作用域中。 由于 icptr 是并集成员并且具有相同的地址,因此一次只能使用其中一个成员。 对成员 cptr 的分配将更改成员 i的值。
void f() {
  union { int i; char* cptr ; };
  /* . . . */
  i = 5;
  cptr = "string_in_union"; // Overrides the value 5.
}

仅限 C + +匿名工会不能有受保护或私人的成员,也不能有成员职能。 全局或命名空间匿名联合必须使用关键字 static 进行声明。仅限 C + +

C11