32.链表的的实现
开发步骤:
1. 建立抽象 自然语言描述
2. 建立接口 list.h 编写类型的定义、结构体的定义、宏的定义、函数声明、include等内容
3. 实现接口 list.c 编写函数的实现
4. 使用接口 main.c list的使用
5. 编译过程
编译环境 : unix 、linux
方法 1 :gcc main.c list.c 生成的可执行文件为a.out
方法 2 : gcc -o main main.c list.c ;./main 生成指定名称的可执行文件main
注意事项 : 三个文件要放在同一文件夹内编译
1. list.h
/* list.h -- 简单链表类型的头文件 */ #ifndef LIST_H_ #define LIST_H_ #include <stdbool.h> /* C99 特性 */ /* 特定程序说明 */ #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
/* list.c -- 支持链表操作的函数 */ #include <stdio.h> #include <stdlib.h> #include "list.h" /* 局部函数原型 */ static void CopyToNode(Item item, Node * pnode); //void show(); /* 接口函数 */ /* 把链表设置为空 */ void InitializeList(List * plist) { *plist = NULL; } /* 如果链表为空,返回true */ bool ListIsEmpty(const List *plist) { if (*plist == NULL) return true; else return false; } /* 如果链表已满,返回true*/ 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; /* 空链pnew表,所有把 pnew放在链表的开头*/ } else { while (scan->next != NULL) scan = scan->next; /* 找到链表的末尾*/ scan->next = ; /* 把pnew添加到链表的末尾*/ } printf("添加节点成功,title:%s ,rating:%d ,pt:%p\n", item.title, item.rating, &item ); 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; /* 前进至下一个节点*/ } puts("当前链表已清空"); } /* 局部函数定义 */ /* 把一个项拷贝到节点中 */ static void CopyToNode(Item item, Node * pnode) { pnode->item =item; /* 拷贝结构*/ }
3. main.c
#include <stdio.h> #include <stdlib.h> /* 提供exit()的原型*/ #include <string.h> /* 提供strchr()原型*/ //#include "list.c" #include "list.h" /* 定义List、Item*/ void echo(Item temp); void testList(); void testList2(); char * s_gets(char *st, int n); int main(int argc, char const *argv[]) { //测试接口1 //testList(); //测试接口2 testList(); return 0; } /* 接收键盘录入字符串,并清除缓冲区*/ char * s_gets(char *st, int n) { char * ret_val; char * find; ret_val = fgets(st, n, stdin); /* 将键盘录入内容存储到st(字符数组),当录入查过函数指定字节时,多余的部分会存储在缓冲区内*/ //处理st中的\n和清空缓冲区 if (ret_val) /* 所有不为0和NULL的值都为 true*/ { find = strchr(st, '\n'); /* 判断指定字符串是否含有指定字符,有则返回该字符的指针,没有返回NULL*/ if (find) /* 当指针不为NULL时,也就是包含\n时*/ *find = '\0'; /* 将\n修改为\0*/ else while (getchar() != '\n') /* 清空缓冲区*/ continue; } return ret_val; } //打印方法的实现 void echo(Item temp) { printf("title:%s rating:%d pt:%p\n", temp.title, temp.rating, &temp); } //测试链表的方法1 void testList() { //创建链表 Node * List movies; //创建项 Item i1; //为项赋值 i1.title[0] = 'g'; i1.title[1] = 'a'; i1.title[2] = 'o'; i1.title[3] = 'c'; i1.title[4] = 'u'; i1.title[5] = 'n'; i1.title[6] = '\0'; i1.rating = 99; //初始化链表 InitializeList(&movies); //为链表添加节点 AddItem(i1,&movies); //当前节点数量 unsigned int size = ListItemCount(&movies); printf("当前链表的节点数:%d\n", size ); Item i2 = i1; i2.rating = 98; //为链表添加节点2 AddItem(i2,&movies); printf("当前链表的节点数:%d\n", ListItemCount(&movies) ); Item i3 = i1; i3.rating = 97; //为链表添加节点2 AddItem(i3,&movies); printf("当前链表的节点数:%d\n", ListItemCount(&movies) ); //遍历链表 puts("开始遍历链表======"); Traverse(&movies,echo); //清空链表 EmptyTheList(&movies); //判断链表是否为空 bool isRmpty = ListIsEmpty(&movies); printf("当前链表是否为空 : %d\n", isRmpty ); //判断链表是否为满 bool isFull = ListIsFull(&movies); printf("当前链表是否为满 : %d\n", isFull ); echo(i1); echo(i2); echo(i3); } void testList2() { List movies; Item temp; /* 初始化 */ InitializeList(&movies); /* 确定是否有内存可用*/ if ( ListIsFull(&movies)) { fprintf(stderr, "No memory avaliable! Bye!\n" ); exit(1); } // /* 获取用户输入并存储*/ // puts("Enter first movie titile:"); while (s_gets(temp.title, TSIZE) != 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 is now full."); break; } puts("Enter next movie title (empty line to stop)"); } /* 遍历链表*/ if (ListIsEmpty(&movies)) printf("No data entered.\n" ); else { printf("Here is the movie list:\n" ); Traverse(&movies,echo); } printf("You entered %d movies.\n", ListItemCount(&movies)); /* 是否内存*/ EmptyTheList(&movies); printf("Bye!\n" ); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界