C语言:链表实现的一个实例

时间:2021-03-05 12:52:11

问题:写一个程序输入你一年看过的所有电影以及每部电影的各种信息(简化问题:每部电影只要求输入片名和评价)

链表实现:

#include<stdio.h>
#include<stdlib.h>//提供malloc()原型
#include<string.h>
#define TSIZE 45
struct film{
    char title[TSIZE];
    int rating;
    struct film * next;
};

int main()
{
    struct film * head = NULL;
    struct film * prev, *current;
    char input[TSIZE];

    //收集并储存信息
    puts("Enter first movie title:");
    while(gets(input)!=NULL&&input[0]!='\0'){
        current = (struct film *)malloc(sizeof(struct film));
        if(head==NULL)
           head=current;
        else
           prev->next=current;
        current->next=NULL;
        strcpy(current->title, input);
        puts("Enter your rating <0-10>:");
        scanf("%d",&current->rating);
        while(getchar()!='\n')
            continue;
        puts("Enter next movie title(empty line to stop):");
        prev=current;
    }

    //给出电影列表
    if(head==NULL)
       printf("No data entered.");
    else
       printf("Here is the movie list:\n");
    current=head;
    while(current!=NULL){
        printf("Movie:%s Rating:%d\n",current->title,current->rating);
        current=current->next;
    } 

    //释放所分配的内存
    current=head;
    while(current!=NULL){
        free(current);
        current=current->next;
    }
    printf("Bye!\n");
    return 0;
}

程序分析:

不使用head遍历整个列表而使用一个新指针current是因为head会改变head的值,这样程序将不再能找到列表的开始处。
由malloc()分配的内存在程序终止时虽然会自动清理,但仍要记得调用free()来释放malloc()分配的内存,养成良好的习惯。

反思:

程序没有检查malloc()是否找到需要的内存,并且没有提供删除列表中项目的功能。

抽象数据类型实现:

1.构造接口

list.h接口文件

#ifndef LIST_H_
#define LIST_H_
#include<stdbool.h>

/*特定程序的声明*/
#define TSIZE 45
struct film
{
    char title[TSIZE];
    int rating;
};

/*一般类型定义*/
typedef struct film Item;

typedef struct node
{
    Item item;
    struct node * next;
}Node;

typedef Node * List;
/*函数原型*/
/*操作:初始化一个列表*/
/*操作前:plist指向一个列表*/
/*操作后:该列表被初始化为空列表*/
void InitializeList(List * plist);

/*操作:确定列表是否为空列表*/
/*操作前:plist指向一个已初始化的列表*/
/*操作后:如果该列表为空则返回true;否则返回false*/
bool ListIsEmpty(const List * plist);

/*操作:确定列表是否已满*/
/*操作前:plist指向一个已初始化的列表*/
/*操作后:如果该列表为空则返回true;否则返回false*/
bool ListIsFull(const List * plist); 

/*操作:确定列表中项目的个数*/
/*操作前:plist指向一个已初始化的列表*/
/*操作后:返回该列表中项目的个数*/
unsigned int ListItemCount(const List * plist);

/*操作:在列表尾部添加一个项目*/
/*操作前:item是要被增加到列表的项目 plist指向一个已初始化的列表*/
/*操作后:如果可能的话,在列表尾部添加一个新项目,函数返回true,否则函数返回false*/
bool AddItem(Item item,List * plist);

/*操作:把一个函数作用于列表中的每个项目*/
/*操作前:plist指向一个已初始化的列表 pfun指向一个函数,该函数接受一个Item参数并且无返回值*/
/*操作后:pfun指向的函数被作用到列表中的每个项目一次*/
void Traverse(const List * plist,void(* pfun)(Item item));

/*操作:释放已分配的内存(如果有)*/
/*操作前:plist指向一个已初始化的列表*/
/*操作后:为该列表分配的内存已被释放并且该列表被置为空列表*/
void EmptyTheList(List * plist);

#endif

2.实现接口:

list.c文件

#include<stdio.h>
#include<stdlib.h>
#include "list.h"
/*局部函数原型*/
static void CopyToNode(Item item,Node * pnode);

/*接口函数*/
/*把列表设置为空列表*/
void InitializeList(List * plist)
{
    * plist = NULL;
}

/*如果列表为空则返回真*/
bool ListIsEmpty(const List * plist)
{
    if(* plist == NULL)
        return true;
    else
        return false;
}

/*如果列表已满则返回真*/
bool ListIsFull(const List * plist)
{
    Node * pt;
    bool full;

    pt = (Node *)malloc(sizeof(Node));
    if(pt==NULL)
        full=true;
    else
        full=false;
    free(pt);
    return full;
}

/*返回节点数*/
unsigned int ListItemCount(const List * plist)
{
    unsigned int count = 0;
    Node * pnode = *plist;
    while(pnode!=NULL)
    {
        ++count;
        pnode=pnode->next;
    }
    return count;
}

/*创建存放项目的节点,并把它添加到由plist指向的列表(较慢的实现方法)尾部*/
bool AddItem(Item item,List * plist)
{
    Node * pnew;
    Node * scan = *plist;

    pnew = (Node *)malloc(sizeof(Node));
    if(pnew==NULL)
        return false;

    CopyToNode(item,pnew);
    pnew->next=NULL;
    if(scan==NULL)
        *plist=pnew;
    else
    {
        while(scan->next!=NULL)
            scan=scan->next;
        scan->next=pnew;
    }
    return true;
}

/*访问每个节点并对它们分别执行由pfun指向的函数*/
void Traverse(const List * plist,void(*pfun)(Item item))
{
    Node *pnode=*plist;
    while(pnode!=NULL)
    {
        (*pfun)(pnode->item);
        pnode=pnode->next;
    }
}

/*释放由malloc()分配的内存*/
/*把列表指针设置为NULL*/
void EmptyTheList(List * plist)
{
    Node * psave;
    while(*plist!=NULL)
    {
        psave=(*plist)->next;
        free(*plist);
        *plist=psave;
    }
}

/*局部函数定义*/
/*把一个项目复制到一个节点中*/
static void CopyToNode(Item item,Node *pnode)
{
    pnode->item=item;
}

3.使用接口:

film.c

#include<stdio.h>
#include<stdlib.h>
#include "list.h"

void showmovies(Item item);

int main(void)
{
    List movies;
    Item temp;

/*初始化*/
    InitializeList(&movies);
    if(ListIsFull(movies))
    {
        fprintf(stderr, "No memory available!Bye!\n");
        exit(1);
    }

/*收集并存储*/
    puts("Enter first movie title:");
    while(gets(temp.title)!=NULL && temp.title[0]!='\0')
    {
        puts("Enter your rating<0-10>:");
        scanf("%d",&temp.rating);
        while(getchar()!='\n')
            continue;
        if(AddItem(temp,&movies)==false)
        {
            fprintf(stderr,"Problem allocating memory\n");
            break;
        }
        if(ListIsFull(movies))
        {
            puts("The list if now full.");
            break;
        }
        puts("Enter next movie title(empty line to stop):");
    }
/*显示*/
    if(ListIsEmpty(movies))
        printf("No data entered.");
    else
    {
        printf("Here is the movie list:\n");
        Traverse(&movies,showmovies);
    }
    printf("You entered %d movies.\n",ListItemCount(&movies));

/*清除*/
    EmptyTheList(&movies);
    printf("Bye!\n");
    return 0;
}

void showmovies(Item item)
{
    printf("Movies:%s Rating:%d\n",item.title,item.rating);
} 

注意:

整个程序由三个文件组成,要运行这个程序,必须编译并链接film.c和list.c(关于编译多文件程序),工程建立如下:
C语言:链表实现的一个实例

反思:

使用ADT方法带来了什么?
1.使用链表的方式暴露了所有编程的细节,而使用ADT方法则隐藏了这些细节,并用与任务直接相关的语言来表达程序。
2.list.h和list.c文件共同组成可重用的资源。如果需要另一个简单列表,仍可用这些头文件。

程序新知:

1.“C预处理器和C库”中的#ifndef技术对多次包含一个文件提供保护
2.“文件输入输出”fprintfgetc()函数等的使用
3.编译多文件程序