c语言 链表_C语言从数组到链表

79bbd77e0bc875a05bc302a7301024d4.png

绘图和各种工具

理想的情况是,用户可以不确定地添加数据(或者不断添加数据直到用完内存量),而不是先指定要输入多少项,也不用让程序分配多余的空间。这可以通过在输入每一项后调用malloc()分配正好能存储该项的空间。如果用户输入3部影片,程序就调用malloc()3次;如果用户输入300部影片,程序就调用malloc()300次。 不过,我们又制造了另一个麻烦。比较一下,一种方法是调用malloc()一次,为300个film结构请求分配足够的空间;另一种方法是调用malloc()300次,分别为每个file结构请求分配足够的空间。前者分配的是连续的内存块,只需要一个单独的指向struct变量(film)的指针,该指针指向已分配块中的第1个结构。简单的数组表示法让指针访问块中的每个结构,如前面代码段所示。第2种方法的问题是,无法保证每次调用malloc()都能分配到连续的内存块。这意味着结构不一定被连续存储(见图17.1)。因此,与第1种方法存储一个指向300个结构块的指针相比,你需要存储300个指针,每个指针指向一个单独存储的结构。

2a9ba22177d338448bf3610cb89012f7.png

Allocating structures in a block versus allocating them individually.

一种解决方法是创建一个大型的指针数组,并在分配新结构时逐个给这些指针赋值,但是我们不打算使用这种方法:

#define TSIZE  45               /* size of array to hold titles    */#define FMAX   500              /* maximum number of film titles   */struct film {    char title[TSIZE];    int rating;};...    struct film * movies[FMAX]; /* array of pointers to structures */    int i;    ...    movies[i] = (struct film *) malloc (sizeof (struct film));

如果用不完500个指针,这种方法节约了大量的内存,因为内含500个指针的数组比内含500个结构的数组所占的内存少得多。尽管如此,如果用不到500个指针,还是浪费了不少空间。而且,这样还是有500个结构的限制。

还有一种更好的方法。每次使用malloc()为新结构分配空间时,也为新指针分配空间。但是,还得需要另一个指针来跟踪新分配的指针,用于跟踪新指针的指针本身,也需要一个指针来跟踪,以此类推。要重新定义结构才能解决这个潜在的问题,即每个结构中包含指向next结构的指针。然后,当创建新结构时,可以把该结构的地址存储在上一个结构中。简而言之,可以这样定义film结构:

#define TSIZE  45      /* size of array to hold titles  */struct film {    char title[TSIZE];    int rating;    struct film * next;};

虽然结构不能含有与本身类型相同的结构,但是可以含有指向同类型结构的指针。这种定义是定义链表(linked list)的基础,链表中的每一项都包含着在何处能找到下一项的信息。 在学习链表的代码之前,我们先从概念上理解一个链表。假设用户输入的片名是Modern-Times,等级为10。程序将为film类型的结构分配空间,把字符串ModernTimes拷贝到结构中的title成员中,然后设置rating成员为10。为了表明该结构后面没有其他结构,程序要把next成员指针设置为NULL(NULL是一个定义在stdio.h头文件中的符号常量,表示空指针)。当然,还需要一个单独的指针存储第1个结构的地址,该指针被称为头指针(head-pointer)。头指针指向链表中的第1项。图17.2演示了这种结构(为节约图片空间,压缩了title成员中的空白)。

5d366dfaa4c4e69f3102980ded6b7946.png

现在,假设用户输入第2部电影及其评级,如Midnight in Paris和8。程序为第2个film类型结构分配空间,把新结构的地址存储在第1个结构的next成员中(擦写了之前存储在该成员中的NULL),这样链表中第1个结构中的next指针指向第2个结构。然后程序把Midnight in Paris和8拷贝到新结构中,并把第2个结构中的next成员设置为NULL,表明该结构是链表中的最后一个结构。图17.3演示了这两个项。

4cc02431e1f8bf2dc3f5ce5a6ad2dca8.png

Linked list with two items.

每加入一部新电影,就以相同的方式来处理。新结构的地址将存储在上一个结构中,新信息存储在新结构中,而且新结构中的next成员设置为NULL。从而建立起如图17.4所示的链表。

8cfae94ae397e8b5065321cde2b7e172.png

假设要显示这个链表,每显示一项,就可以根据该项中已存储的地址来定位下一个待显示的项。然而,这种方案能正常运行,还需要一个指针存储链表中第1项的地址,因为链表中没有其他项存储该项的地址。此时,头指针就派上了用场。

1 使用链表

从概念上了解了链表的工作原理,接着我们来实现它。修改了程序films2.c,用链表而不是数组来存储电影信息。

The films2.c Program

/* films2.c -- using a linked list of structures */#include #include       /* has the malloc prototype      */#include       /* has the strcpy prototype      */#define TSIZE    45      /* size of array to hold title   */struct film {    char title[TSIZE];    int rating;    struct film * next;  /* points to next struct in list */};char * s_gets(char * st, int n);int main(void){    struct film * head = NULL;    struct film * prev, * current;    char input[TSIZE];​/* Gather  and store information          */    puts("Enter first movie title:");    while (s_gets(input, TSIZE) != NULL && input[0] != '0')    {        current = (struct film *) malloc(sizeof(struct film));        if (head == NULL)       /* first structure       */            head = current;        else                    /* subsequent structures */            prev->next = current;        current->next = NULL;        strcpy(current->title, input);        puts("Enter your rating <0-10>:");        scanf("%d", ¤t->rating);        while(getchar() != 'n')            continue;        puts("Enter next movie title (empty line to stop):");        prev = current;    }​/* Show list of movies                    */    if (head == NULL)        printf("No data entered. ");    else        printf ("Here is the movie list:n");    current = head;    while (current != NULL)    {        printf("Movie: %s  Rating: %dn",               current->title, current->rating);        current = current->next;    }​/* Program done, so free allocated memory */    current = head;    while (current != NULL)    {        free(current);        current = current->next;    }    printf("Bye!n");    return 0;}char * s_gets(char * st, int n){    char * ret_val;    char * find;    ret_val = fgets(st, n, stdin);    if (ret_val)    {        find = strchr(st, 'n');   // look for newline        if (find)                  // if the address is not NULL,            *find = '0';          // place a null character there        else            while (getchar() != 'n')                continue;          // dispose of rest of line    }    return ret_val;}

该程序用链表执行两个任务。第1个任务是,构造一个链表,把用户输入的数据存储在链表中。第2个任务是,显示链表。显示链表的任务比较简单,所以我们先来讨论它。

1..1 显示链表

显示链表从设置一个指向第1个结构的指针(名为current)开始。由于头指针(名为head)已经指向链表中的第1个结构,所以可以用下面的代码来完成:

current = head;

然后,可以使用指针表示法访问结构的成员:

printf("Movie: %s  Rating: %dn", current->title, current->rating);

下一步是根据存储在该结构中next成员中的信息,重新设置current指针指向链表中的下一个结构。代码如下:

current = current->next;

完成这些之后,再重复整个过程。当显示到链表中最后一个项时,current将被设置为NULL,因为这是链表最后一个结构中next成员的值。

while (current != NULL){    printf("Movie: %s  Rating: %dn", current->title, current->rating);    current = current->next;}

遍历链表时,为何不直接使用head指针,而要重新创建一个新指针(current)?因为如果使用head会改变head中的值,程序就找不到链表的开始处。

1.2 创建链表

创建链表涉及下面3步:
(1)使用malloc()为结构分配足够的空间;
(2)存储结构的地址;
(3)把当前信息拷贝到结构中。

如无必要不用创建一个结构,所以程序使用临时存储区(input数组)获取用户输入的电影名。如果用户通过键盘模拟EOF或输入一行空行,将退出下面的循环:

while (s_gets(input, TSIZE) != NULL && input[0] != '0')

如果用户进行输入,程序就分配一个结构的空间,并将其地址赋给指针变量current:

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

链表中第1个结构的地址应存储在指针变量head中。随后每个结构的地址应存储在其前一个结构的next成员中。因此,程序要知道它处理的是否是第1个结构。最简单的方法是在程序开始时,把head指针初始化为NULL。然后,程序可以使用head的值进行判断:

if (head == NULL)       /* first structure       */   head = current;else                    /* subsequent structures */   prev->next = current;

在上面的代码中,指针prev指向上一次分配的结构。 接下来,必须为结构成员设置合适的值。尤其是,把next成员设置为NULL,表明当前结构是链表的最后一个结构。还要把input数组中的电影名拷贝到title成员中,而且要给rating成员提供一个值。如下代码所示:

current->next = NULL;strcpy(current->title, input);puts("Enter your rating <0-10>:");scanf("%d", ¤t->rating);

由于sgets()限制了只能输入TSIZE-1个字符,所以用strcpy()函数把input数组中的字符串拷贝到title成员很安全。 最后,要为下一次输入做好准备。尤其是,要设置prev指向当前结构。因为在用户输入下一部电影且程序为新结构分配空间后,当前结构将成为新结构的上一个结构,所以程序在循环末尾这样设置该指针:

prev = current;

程序是否能正常运行?下面是该程序的一个运行示例:

Enter first movie title: Spirited Away Enter your rating <0-10>: 9 Enter next movie title (empty line to stop): The Duelists Enter your rating <0-10>: 8 Enter next movie title (empty line to stop): Devil Dog: The Mound of Hound Enter your rating <0-10>: 1 Enter next movie title (empty line to stop):

Here is the movie list: Movie: Spirited Away Rating: 9 Movie: The Duelists Rating: 8 Movie: Devil Dog: The Mound of Hound Rating: 1 Bye!

1.3 释放链表

在许多环境中,程序结束时都会自动释放malloc()分配的内存。但是,最好还是成对调用malloc()和free()。因此,程序在清理内存时为每个已分配的结构都调用了free()函数:

current = head;while (current != NULL){    free(current);    current = current->next;}

2 反思

films2.c程序还有些不足。例如,程序没有检查malloc()是否成功请求到内存,也无法删除链表中的项。这些不足可以弥补。例如,添加代码检查malloc()的返回值是否是NULL(返回NULL说明未获得所需内存)。

如果程序要删除链表中的项,还要编写更多的代码。 这种用特定方法解决特定问题,并且在需要时才添加相关功能的编程方式通常不是最好的解决方案。另一方面,通常都无法预料程序要完成的所有任务。随着编程项目越来越大,一个程序员或编程团队事先计划好一切模式,越来越不现实。很多成功的大型程序都是由成功的小型程序逐步发展而来。 如果要修改程序,首先应该强调最初的设计,并简化其他细节。

程序films2.c中的程序示例没有遵循这个原则,它把概念模型和代码细节混在一起。例如,该程序的概念模型是在一个链表中添加项,但是程序却把一些细节(如,malloc()和current->next指针)放在最明显的位置,没有突出接口。如果程序能以某种方式强调给链表添加项,并隐藏具体的处理细节(如调用内存管理函数和设置指针)会更好。把用户接口和代码细节分开的程序,更容易理解和更新。学习下面的内容就可以实现这些目标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值