关键字struct能定义各种类型的变量集合,称为结构(structure),并把它们视为一个单元。下面是一个简单的结构声明例子:
struct horse
{
int age;
int height;
} Silver;
这个例子声明了一个结构horse。horse不是一个变量名,而是一个新的类型,这个类型名称通常称为结构标记符(structure tag)或标记符名称(tag name)。结构标记符的命名方式和我们熟悉的变量名相同。
注意:
结构标记符可以和变量使用相同的名称,但最好不要这么做,因为这会使代码难以理解。
结构内的变量名称age和height称为结构成员(structure members)。在这个例子中,它们都是int类型。结构成员出现在结构标记符名称horse后的大括号内。
在这个结构例子中,结构的一个实例Silver是在定义结构时声明的。它是一个horse类型的变量,只要使用变量名称Silver,它都包含两个结构成员:age和height。
struct horse
{
int age;
int height;
char name[20];
char father[20];
char mother[20];
};
struct horse My_first_horse; /* Structure variable declaration */
然后,使用下面的语句为My_first_horse结构的成员name读入数据:
scanf("%s", My_first_horse.name ); /* Read the horse's name */
这里不需要使用寻址运算符(&),因为结构的成员name是一个数组,所以将数组第一个元素的地址隐式传送给函数scanf()。
接下来给horse的成员age读入数值:
scanf("%d", &My_first_horse.age ); /* Read the horse's age */
由于这个成员是int类型的变量,所以必须使用&运算符传递这个结构成员的地址。
未命名的结构
不—定要给结构指定标记符名字。用一条语句声明结构和该结构的实例时,可以省略标记符名字。在上一个例子中,声明了horse类型和该类型的实例My_first_horse,也可以改为:
struct
{ /* Structure declaration and... */
int age;
int height;
char name[20];
char father[20];
char mother[20];
} My_first_horse; /* ...structure variable declaration combined */
使用这种方法的最大缺点是不能在其他语句中定义这个结构的其他实例。这个结构类型的所有变量必须在一行语句中定义。
结构数组
struct horse /* Structure declaration */
{
int age;
int height;
char name[20];
char father[20];
char mother[20];
};
struct horse My_horses[50]; /* Structure array declaration */
scanf("%s", My_horses[hcount].name ); /* Read the horse's name */
从上述语句可以看出,引用结构数组的一个元素成员的方法非常简单。这个结构数组名称将索引放在方括号内,后跟句点和成员名。如果想引用这个结构第4个元素的name数组的第3个元素,可以使用:
My_horses[3].name[2]
注意:
结构数组的索引与其他类型的数组一样,也是从0开始,所以结构数组的第4个元素的索引值是3,而其成员数组的第3个元素的索引值是2。
表达式中的结构
My_horses[1] = My horses[2];
这行语句会将结构My_horses[2]的所有成员复制到结构My_horses[1]中,使这两个结构完全相同。使用整个结构的另一个操作是使用&运算符提取地址。但是不能对整个结构执行加、比较或其他操作
结构指针
要获得结构的地址,就需要使用结构的指针。由于需要的是结构的地址,因此需要声明结构的指针。结构指针的声明方式和声明其他类型的指针变量相同,例如:
struct horse *phorse;
phorse = &My_horses[1];
这条语句声明了一个phorse指针,它可以存储horse类型的结构地址,phorse指向结构My_horses[1].
可以通过phorse指针引用这个结构的元素。因此,如果要显示这个结构成员的名字,可以编写如下语句:
printf("/nThe name is %s.", (*phorse).name);
取消引用指针的括号是非常重要的,因为成员选择运算符(句点)的优先级高于取消引用指针运算符*。这个操作还有另一种方法,且更容易理解。将上面的语句改写成:
printf("/nThe name is %s.", phorse->name);
这就不需要括号或星号了。–>运算符是一个负号后跟一个大于号。这个运算符有时也称为成员指针运算符。
为结构动态分配内存
struct horse *phorse[50]; /* pointer to structure array declaration */
仅定义了50个horse类型的结构指针,还要将结构放在指针指向的地址中,如下:
phorse[hcount] = (struct horse*) malloc(sizeof(struct horse));
这行语句会给每个结构分配内存空间。malloc()函数会分配变元指定的字节数,并将所分配内存块的地址返回为void类型的指针。这个例子使用sizeof运算符计算需要的字节数。
使用sizeof运算符可以计算出结构所占的字节数,其结果不一定对应于结构中各个成员所占的字节数总和,如果自己计算,就很容易出错。
除了char类型的变量之外,2字节变量的起始地址常常是2的倍数,4字节变量的起始地址常常是4的倍数,依此类推。这称为边界调整(boundary alignment),它和C语言无关,而是硬件的要求。以这种方式在内存中存储变量,可以更快地在处理器和内存之间传递数据,但不同类型的成员变量之间会有未使用的字节。这些未使用的字节也必须算在结构的字节数中,如图11-2所示。
图11-2 边界调整在内存分配上的影响
malloc()函数返回的值是一个void指针,因此必须用表达式(struct horse*)将它转换成所需要的类型
然后释放内存:
free(phorse[i]);
将一个结构作为另一个结构的成员
struct Date
{
int day;
int month;
int year;
};
现在定义结构horse,其中包含出生日期变量,如下所示:
struct horse
{
struct Date dob;
int height;
char name[20];
char father[20];
char mother[20];
};
Dobbin.dob.day = 5;
可以将第一个结构用作第二个结构的成员,再将第二个结构作为第三个结构的成员,依此类推。但C编译器只允许结构最多有15层。如果结构有这么多层,则引用最底层的成员时,需要输入所有的结构成员名称
如果使用Dobbin.dob,就会引用一个Date类型的结构变量。Date不是一个基本类型,而是一个结构,所以只能使用下面的方式赋值:
Trigger.dob = Dobbin.dob;
将结构指针用作结构成员
任何指针都可以是结构的成员,包含结构指针在内。结构成员指针可以指向相同类型的结构。如,horse类型的结构可以含有一个指向horse类型结构的指针。
结构中的位字段
位字段(bit-fields)提供的机制允许定义变量来表示一个整数中的一个或多个位,这样,就不需要为每个位明确指定成员名称了。
注意:
位字段常用在必须节省内存的情况下。这种情况目前比较少见。与标准类型的变量相比,位字段会明显降低程序执行的速度。因此,必须在节省内存和程序执行速度之间作一个抉择。在大多数情况下,不需要使用位字段,使用它甚至是不理想的,但读者应了解它。
下面是一个声明位字段的例子:
struct
{
unsigned int flag1 : 1;
unsigned int flag2 : 1;
unsigned int flag3 : 2;
unsigned int flag4 : 3;
} indicators;
上述语句定义了indicators变量,它是匿名结构的一个实例,包含4个位字段,分别是flagl~flag4。它们全部存储在一个字符组(word)中,如图11-4所示。
图11-4 结构中的位字段
前两个位字段在定义中指定为1,表示它们是一个位,其值是0或1。第三个位字段flag3有两个位,其值是0~3。最后一个flag4有三个位,其值是0~7。引用这些位字段的方式和引用一般结构成员的方式相同。例如:
indicators.flag4 = 5;
indicators.flag3 = indicators.flag1 = 1;
几乎没什么机会用到这个功能,这里介绍它只是为了讨论完整性,如果哪天缺乏内存,就可以考虑使用它。