### 结构体类型

结构体是一组变量的集合，它能够将不同数据类型的对象组合为一个整体，以描述一个新的对象。例如，描述一个学生信息。学生信息需要包括姓名、年龄、性别、地址等信息。这样，由姓名、年龄、性别、地址等内容就构成了学生对象。在程序中为了描述类似于“学生”这样的复合对象（由多个成分构成），可以使用结构体类型。在 C++中，结构体类型的声明使用 struct 关键字。下面的语句使用结构体类型来描述学生信息。

const int CHAR_LEN = 128;

struct Student //定义一个结构体

{

char szName[CHAR_LEN]; //姓名

int nAge; //年龄

char szSex[CHAR_LEN]; //性别

char szAddress[CHAR_LEN]; //地址

};

其中，struct 是关键字，Student 是结构体类型名称，它可以是任意合法的标识符。大括号中定义的变量是结构体 Student 的成员，有时也被称之为字段（Field）或元素（Element）。在结构体定义的结尾处是一个分号，主要分号不能够省略。

在定义一个结构体类型之后，可以定义一个结构体变量。在 C 语言中定义结构变量的格式为：

struct 结构体类型 结构体类型变量;

例如，下面的代码采用 C 语言的形式定义结构体变量。

struct Student stdnt;

在 C++语言中定义结构体变量的格式与定义普通变量的格式相同。例如：

Student stdnt;

当定义一个结构体变量时，编译器将为变量分配足够的空间以容纳所有结构体中所有的成员。在声明结构体类型时，也可以直接定义结构体变量。例如：

const int CHAR_LEN = 128;

struct Student //定义一个结构体

{

char szName[CHAR_LEN]; //姓名

int nAge; //年龄

char szSex[CHAR_LEN]; //性别

char szAddress[CHAR_LEN]; //地址

}stdnt;

上述代码在声明结构体 Student 的同时，定义了一个结构体变量 stdnt。此外，在定义结构体时，如果只需要一次定义结构体变量（在其他地方不要定义该类型的结构体变量），可以不写结构类型的名称，而只给出结构体变量。例如：

const int CHAR_LEN = 128;

struct //定义一个结构体

{

char szName[CHAR_LEN]; //姓名

int nAge; //年龄

char szSex[CHAR_LEN]; //性别

char szAddress[CHAR_LEN]; //地址

}stdnt;

在定义结构体变量之后，为了应用结构体变量，需要访问结构体中的各个成员。可以使用“.”符号来访问结构体中的成员。例如：

Student stdnt;

stdnt.nAge = 22;

我们知道，两个整型变量之间是可以相互赋值的，那么两个结构体变量之间是否可以相互赋值呢？答案是可以的。下面通过一个实例来说明结构体变量之间的赋值。

结构体变量之间的赋值

#include "stdafx.h"

#include "iostream.h"

const int CHAR_LEN = 128; //定义一个常量

struct Student //定义一个结构体

{

char szName[CHAR_LEN]; //姓名

int nAge; //年龄

char szSex[CHAR_LEN]; //性别

char szAddress[CHAR_LEN]; //地址

};

int main(int argc, char* argv[])

{

Student Jeff; //定义一个结构体对象

Jeff.nAge = 15; //设置结构体对象成员

Student Annie; //定义一个结构体对象

Annie = Jeff; //进行结构体对象赋值

cout << Annie.nAge << endl;

return 0;

}

运行程序，效果如图 1 所示。

图 1 运行结果

从图 1 中可以发现，Annie 对象的 nAge 成员的值为 15，即与 Jeff 对象的 nAge 成员相同。

在定义一个结构体对象时，编译器会依据结构体的定义为对象分配足够的空间以容纳结构体的各个成员。那么结构体究竟为对象分配多少空间呢。我们来分析一下下面结构体的定义。

struct SimpleTypeSet

{

double dbType;

char szType;

int nType;

};

在结构体 SimpleTypeSet 中共包含了 3 个成员，其中 dbType 成员为 double 类型，占用 8 个字节，szType 成员为 char 类型，占用 1 个字节空间，nType 为 int 类型，占用 4 个字节空间。那么整个 SimpleTypeSet 结构应占用 13 个字节空间。下面通过程序来测试一下，测试的方式是使用 sizeof 函数。

int main(int argc, char* argv[])

{

SimpleTypeSet typeSet; //定义一个结构体对象

int nSize = sizeof(typeSet); //获取结构体的大小

cout << "SimpleTypeSet 结构大小: " << nSize << endl; //输出结构体大小

return 0;

}

运行程序，结果如图 2 所示。

图 2 计算结构体大小

从图 2 中可以发现结构体 SimpleTypeSet 占用的空间是 16 个字节，而非我们之前设想的 13 个字节。问题出在哪里呢？

这涉及到结构体的字节对齐问题。编译器在为结构体变量分配空间时，保证下一个成员的偏移量应为该成员数据类型长度的整数倍。分析一下 SimpleTypeSet 结构在内存中的简单布局。首先为 dbType 成员分配空间，假设起始偏移位置从 0 开始，dbType 成员将占用 0，1，2，3，4，5，6，7 共 8 个字节。接下来为成员 szType 分配空间，由于 char 类型占用 1 个字节，szType 将占据 8 的位置，因为当前位置 8 与 1 是整除的。最后为 nType 成员分配空间，该成员为 int 类型，占用 4 个字节。当前偏移位置为 9，并不是 4 的整数倍，因此需要空出 3 个字节（9、10、11），nType 从 12 的位置开始分配 4 个字节的空间。这样就导致了实际分配的大小与“理论上”的大小不一致。图 3 简要描述了 SimpleTypeSet 结构内存布局。

图 3 SimpleTypeSet 结构内存布局示意图

在开发应用程序时，有时需要在一个字节中表示多项内容。例如，在描述 IP 协议的首部时，其首部长度占 4 位（bit），版本号占 4 位。而程序最小的空间分配单位是一个字节，即 8 位。在定义描述 IP 协议首部的结构体时，该如何实现呢？ C/C++语言提供了位域，允许用户单独访问一位数据。例如，下面的代码定义了一个 IP 结构体，用于描述首部长度和版本号。

typedef struct HeadIP

{

unsigned char headerlen:4; //首部长度,占 4 位

unsigned char version:4; //版本,占 4 位

}

其中，headerlen 成员类型为无符号字符型，理应占 1 个字节（8 位），通过使用位域符号“:”和长度“4”，headerlen 成员只占了“半”个字节——4 位。在定义位域字段时，也可以不指定成员名称，这样可以预留一些空间。例如：

struct FieldZone

{

unsigned char: 4;

unsigned char memTwo:2;

unsigned char memThree:1;

};

用户在访问 memTwo 成员时，将直接从一个字节的第 5 位开始读取数据。