首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

11.2  再探结构成员

前面说过,所有基本数据类型(包含数组)都可以成为结构的成员。除此之外,还可以把一个结构作为另一个结构的成员,不仅指针可以是结构的成员,结构指针也可以是结构的成员。

使用结构为编程打开了一个全新领域的大门,同时也增加了潜在的危机。下面逐一探讨这些内容,深入了解结构成员的组成。

11.2.1  将一个结构作为另一个结构的成员

本章的开头为满足马饲养员的需要,设计了一个程序,处理每匹马的各种数据,包括名字、身高和生日等,但程序11.1用年龄代替了生日。其部分原因是日期处理起来比较麻烦,要用3个数值表示,还要处理闰年的问题。现在准备将一个结构作为另一个结构的成员来处理日期。

可以定义一个用于保存日期的结构类型。下面的语句用标记符名称Date定义了这个结构:

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];

};

现在结构中有一个变量成员,它代表马的出生日期的结构。接下来用通常的语句定义一个horse结构的实例,如下所示:

struct  horse  Dobbin;

用与前面相同的语句为成员height设定值:

Dobbin.height = 14;

要在一系列赋值语句中设定出生日期,可以使用下面的逻辑:

Dobbin.dob.day  =  5;

Dobbin.dob.month  =  12;

Dobbin.dob.year  =  1962;

这是一匹很老的马,表达式Dobbin.dob.day引用了int类型的变量,所以可以将它用于算术表达式或比较表达式。但如果使用Dobbin.dob,就会引用一个Date类型的结构变量。Date不是一个基本类型,而是一个结构,所以只能使用下面的方式赋值:

Trigger.dob  =  Dobbin.dob;

这行语句表示两匹马是双胞胎,但不能保证事实如此。

可以将第一个结构用作第二个结构的成员,再将第二个结构作为第三个结构的成员,依此类推。但C编译器只允许结构最多有15层。如果结构有这么多层,则引用最底层的成员时,需要输入所有的结构成员名称。

11.2.2  声明结构中的结构

可以在horse结构的定义中声明Date结构,如下:

struct  horse

{

struct  Date

{

int day;

int month;

int year;

}  dob;

int  height;

char  name[20];

char  father[20];

char  mother[20];

};

这个声明将Date结构声明放在horse结构的定义内,因此不能在horse结构的外部声明Date变量。当然,每个horse类型的变量都包含Date类型的成员dob。但下面的语句:

struct  Date  my_date;

会导致编译错误。错误信息会说明Date结构类型未定义。如果需要在horse结构的外部使用Date,就必须将它定义在horse结构之外。

11.2.3  将结构指针用作结构成员

任何指针都可以是结构的成员,包含结构指针在内。结构成员指针可以指向相同类型的结构。例如,horse类型的结构可以含有一个指向horse类型结构的指针。

试试看:将结构指针用作结构成员

修改前一个例子,让结构含有指向同类型结构的指针:

/* Program 11.4 Daisy chaining the horses */

#include <stdio.h>

#include <ctype.h>

#include <stdlib.h>

int main(void)

{

struct horse                     /* Structure declaration */

{

int age;

int height;

char name[20];

char father[20];

char mother[20];

struct horse *next;            /* Pointer to next structure */

};

struct horse *first = NULL;     /* Pointer to first horse */

struct horse *current = NULL;   /* Pointer to current horse */

struct horse *previous = NULL; /* Pointer to previous horse */

char test = '\0';               /* Test value for ending input */

for( ; ; )

{

printf("\nDo you want to enter details of a%s horse (Y or N)? ",

first != NULL?"nother " : "" );

scanf(" %c", &test );

if(tolower(test) == 'n')

break;

/* Allocate memory for a structure */

current = (struct horse*) malloc(sizeof(struct horse));

if(first == NULL)

first = current;             /* Set pointer to first horse */

if(previous != NULL)

previous -> next = current; /* Set next pointer for previous horse */

printf("\nEnter the name of the horse: ");

scanf("%s", current -> name); /* Read the horse's name */

printf("\nHow old is %s? ", current -> name);

scanf("%d", &current -> age); /* Read the horse's age */

printf("\nHow high is %s ( in hands )? ", current -> name );

scanf("%d", &current -> height); /* Read the horse's height */

printf("\nWho is %s's father? ", current -> name);

scanf("%s", current -> father); /* Get the father's name */

printf("\nWho is %s's mother? ", current -> name);

scanf("%s", current -> mother); /* Get the mother's name */

current->next = NULL;            /* In case it's the last... */

previous = current;             /* Save address of last horse */

}

/* Now tell them what we know. */

current = first;                   /* Start at the beginning */

while (current != NULL)            /* As long as we have a valid pointer */

{ /* Output the data*/

printf("\n\n%s is %d years old, %d hands high,",

current->name, current->age, current->height);

printf(" and has %s and %s as parents.", current->father,

current->mother);

previous = current; /* Save the pointer so we can free memory */

current = current->next; /* Get the pointer to the next */

free(previous);                  /* Free memory for the old one */

}

return 0;

}

如果输入相同,这个例子会产生和程序11.3相同的输出。

代码的说明

这次不但没有为结构分配空间,而且只定义了三个指针。这些指针用下面的语句声明和初始化:

struct horse *first = NULL;      /* Pointer to first horse */

struct horse *current = NULL;    /* Pointer to current horse */

struct horse *previous = NULL;   /* Pointer to previous horse */

每个指针都定义成horse结构的指针。first指针仅用于存储第一个结构的地址。第二和第三个指针是工作用的存储器:current存储了正在处理的horse结构的地址,previous跟踪前一个处理过的结构的地址。

horse结构中新增的next成员是指向horse结构的指针。每个horse结构中的next都指向下一个horse的地址,以链接所有的horse结构。但最后一个horse结构例外,它的next设定成NULL。这个结构的其他方面与前面相同,如图11-3所示。

图11-3  链接起来的horse结构

输入循环如下:

for(  ;   ;   )

{

...

}

因为没有使用数组,不需要考虑索引,所以输入循环是一个无限循环。也不需要计算读入了多少组数据,所以不需要使用变量hcount及循环变量i。因为给每个horse结构分配了内存,所以只需接受输入的数据。

循环中的开始语句如下:

printf("\nDo you want to enter details of a%s horse (Y or N)? ",

first != NULL?"nother " : "" );

scanf(" %c", &test );

if(tolower(test) == 'n')

break;

在提示后,如果回答是N或n,就结束循环。否则,就准备接受另一组结构成员。first指针只有在第一次迭代时是NULL,所以在第二次以后的迭代中,其提示信息会和第一次稍有不同。

回答了循环开头的问题后,就执行下面的语句:

current = (struct horse*) malloc(sizeof(struct horse));

if(first == NULL)

first = current;               /* Set pointer to first horse */

if(previous != NULL)

previous -> next = current; /* Set next pointer for previous horse */

每次迭代时,都为当前的结构分配必要的内存。为了精简程序,没有检查malloc()函数是否返回了NULL,但在实际使用时应检查。

如果指针first等于NULL,就表示是第一次迭代,即开始输入第一个结构。因此,将first指针设置为malloc()函数返回的指针值,即current变量存储的值。first中的地址也是访问链中第一个horse结构的关键。可以从first中的地址开始,利用成员next指针得到下一个结构的地址,再依序访问下一个结构,从而到达任何一个horse结构。

如果有下一个结构,就必须将next指针指向这个结构,但只要有下一个结构,就可以确定其地址。因此,在第二次和后续的迭代中,应将当前结构的地址存储到前一个结构的next成员中,前一个结构的地址存放到previous指针中。在第一次迭代中,previous的指针是NULL,所以什么也不做。

在完成了所有的输入语句,到循环的最后,有下面两行语句:

current->next = NULL;   /* In case it's the last…*/

previous = current;     /* Save address of last horse */

在current指向的结构中,next指针设定成NULL,表示这是最后一个结构,没有下一个结构了。如果有下一个结构,指针next会在下一次迭代时修改。指针previous设定成current,然后进入下一次迭代,此时current指向的结构就是previous指向的结构了。

这个程序的优点是生成了horse结构链,在这个链中,每个结构的next成员都指向下一个结构。最后一个结构例外,因为再也没有下一个horse结构了,所以next指针包含NULL,这称为链表。

horse数据放在链表中后,就可以从第一个结构开始,通过指针成员next访问下一个结构。指针next是NULL时,就到达了链表的末尾。这就是为所有输入生成输出表的方式。

在需要处理数量未知的结构的应用程序中,链表非常有用。链表的主要优点是内存的使用和便于处理。存储和处理链表所占用的内存量最少。即使所使用的内存比较分散,也可以从一个结构进入下一个结构。因此,链表可以用于同时处理几个不同类型的对象,每个对象都可以用它自己的链表来处理,以优化内存的使用。但链表也有一个小缺点:数据处理的速度比较慢,尤其是要随机访问数据时,速度更慢。

输出过程说明了如何遍历链表,以访问它,语句如下:

current = first;         /* Start at the beginning */

while (current != NULL) /* As long as we have a valid pointer */

{ /* Output the data*/

printf("\n\n%s is %d years old, %d hands high,",

current->name, current->age, current->height);

printf(" and has %s and %s as parents.", current->father,

current->mother);

previous = current; /* Save the pointer so we can free memory */

current = current->next;   /* Get the pointer to the next */

free(previous);              /* Free memory for the old one */

}

输出循环由current指针控制,它开始时设定成first。而first指针包含链表中第一个结构的地址。循环会遍历链表,显示每个结构的成员,之后把current赋予指向下一个结构的成员next。

结构显示过后就释放其内存。这是很重要的,一旦不再需要引用结构,就释放其内存。但是不能在输出当前结构的所有成员后,马上调用free()函数。必须先引用当前结构的next成员,得到下一个horse结构的指针。

在链表的最后一个结构中,next指针是NULL,因而结束循环。

11.2.4  双向链表

前一个例子创建的链表有一个缺点:只能往前走。其实,只需小小的修改,就可以得到双向链表(doubly linked list),可以双向遍历链表。方法是除了指向下一个结构的指针外,在每个结构中再添加一个指针,存储前一个结构的地址。

试试看:双向链表

修改程序11.4,改成双向链表:

/* Program 11.5 Daisy chaining the horses both ways */

#include <stdio.h>

#include <ctype.h>

#include <stdlib.h>

int main(void)

{

struct horse /* Structure declaration */

{

int age;

int height;

char name[20];

char father[20];

char mother[20];

struct horse *next;          /* Pointer to next structure */

struct horse *previous;      /* Pointer to previous structure */

};

struct horse *first = NULL;    /* Pointer to first horse */

struct horse *current = NULL; /* Pointer to current horse */

struct horse *last = NULL;     /* Pointer to previous horse */

char test = '\0';              /* Test value for ending input */

for( ; ; )

{

printf("\nDo you want to enter details of a%s horse (Y or N)? ",

first == NULL?"nother " : "");

scanf(" %c", &test );

if(tolower(test) == 'n')

break;

/* Allocate memory for each new horse structure */

current = (struct horse*)malloc(sizeof(struct horse));

if( first == NULL )

{

first = current;            /* Set pointer to first horse */

current->previous = NULL;

}

else

{

last->next = current;        /* Set next address for previous horse */

current->previous = last; /* Previous address for current horse */

}

printf("\nEnter the name of the horse: ");

scanf("%s", current -> name ); /* Read the horse's name */

printf("\nHow old is %s? ", current -> name);

scanf("%d", &current -> age); /* Read the horse's age */

printf("\nHow high is %s ( in hands )? ", current -> name);

scanf("%d", &current -> height); /* Read the horse's height */

printf("\nWho is %s's father? ", current -> name);

scanf("%s", current -> father); /* Get the father's name */

printf("\nWho is %s's mother? ", current -> name);

scanf("%s", current -> mother); /* Get the mother's name */

current -> next = NULL; /* In case it's the last horse..*/

last = current;          /* Save address of last horse */

}

/* Now tell them what we know. */

while(current != NULL) /* Output horse data in reverse order */

{

printf("\n\n%s is %d years old, %d hands high,",

current->name, current->age, current->height);

printf(" and has %s and %s as parents.", current->father,

current->mother);

last = current; /* Save pointer to enable memory to be freed */

current = current->previous; /* current points to previous in list */

free(last);                  /* Free memory for the horse we output */

}

return 0;

}

如果输入相同的数据,这个程序会产生和前一个例子相同的结果,只是显示的顺序相反。

代码的说明

开始的指针声明如下:

struct horse *first = NULL;     /* Pointer to first horse */

struct horse *current = NULL;   /* Pointer to current horse */

struct horse *last = NULL;      /* Pointer to previous horse */

把在循环的上一个迭代中输入的horse结构指针名称改成last。这么做并不是必需的,但有助于避免和horse结构中的成员previous混淆。

horse结构的声明如下:

struct horse /* Structure declaration */

{

int age;

int height;

char name[20];

char father[20];

char mother[20];

struct horse *next;      /* Pointer to next structure */

struct horse *previous; /* Pointer to previous structure */

};

现在horse结构有两个指针,一个是往前的指针称为next,另一个是往后的指针称为previous。这样就可以双向遍历链表,这也是在程序的最后可以反向输出数据的原因。

除了输出之外,程序的唯一变化是在输入循环的开头添加了使用结构成员指针previous的语句:

if( first == NULL )

{

first = current;          /* Set pointer to first horse */

current->previous = NULL;

}

else

{

last->next = current;      /* Set next address for previous horse */

current->previous = last; /* Previous address for current horse */

}

这里用if-else取代上一个例子中的两个if语句。唯一的区别是设定了结构成员previous的值。第一个结构的previous设定成NULL,其后的结构都把previous设定成last,last的值是在前一次迭代中存储的。

另一个改变是在输入循环的最后。

last = current; /* Save address of last horse */

添加这行语句,是为了把下一个结构的previous指针设定成相应的值,即变量last中存储的current结构。

输出过程基本上和前一个例子相同,只是从链表中的最后一个结构开始,遍历到第一个结构而已。

11.2.5  结构中的位字段

位字段(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;

几乎没什么机会用到这个功能,这里介绍它只是为了讨论完整性,如果哪天缺乏内存,就可以考虑使用它。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论