超详细注释 C语言实现链表的基本操作

第一次学数据结构的时候,c语言基础实在太差,在尝试用c语言来实现的时候一直碰壁,数据结构书里的代码是伪代码,使用的时候各种内存访问冲突,各种锟斤拷烫烫烫,各种变量级别不同 。于是我又重新学了一遍c语言,并且又看了一本详细阐述c语言指针有关的书,终于明白了,以前为什么出那么多错 。之前一直出错的本质就是没有理解指针 。学完了以后,现在已经可以成功使用C语言实现数据结构中的算法了 。分享一下我的学习成果,希望和我一样的人也能少走弯路 。
我想在函数内完成我想要的操作,之前一直用的是一级指针,可是每次创建完的链表都会内存访问冲突 。后来经过深入的学习,我发现了这个错误的本质和c语言中经典的用swap函数交换两个变量的值到主函数中没有交换的错误是一样的 。然后我就采用了二级指针,解决了这个问题 。
代码中LinkList *Head等价于LNode **Head,
LinkList head等价于LNode *head 。为了便于区分我将二级指针中的头节点的首字母进行了大写 。
下面是头文件“链表.h”,主要放了链表结构的声明以及相关函数的声明 。
//蔚明//链表.h/*我想在函数内完成我想要的操作,之前一直用的是一级指针,可是每次创建完的链表都会内存访问冲突 。后来经过深入的学习,我发现了这个错误的本质和c语言中经典的用swap函数交换两个变量的值到主函数中没有交换的错误是一样的 。然后我就采用了二级指针,解决了这个问题,代码中多次用到二级指针 。代码中LinkList *Head等价于LNode **Head,LinkList head等价于LNode *head 。为了便于区分我将二级指针中的头节点的首字母进行了大写 。*/#ifndef 链表_H#define 链表_H#pragma warning(disable:6031)//在visual studio中使用字符串函数,输入函数时#pragma warning(disable:4996)//会报错(C4996,C6031),使用这两行代码以后可以正常使用 。//也可以"字符串函数名_s()"的方式使用函数,但需要更多参数 。#include "stdio.h"#include "stdlib.h"#define OK 0#define FAIL -1#define ENTER printf("\n");typedef int Elemtype;//可将"int"更换为任意类型变量 。typedef struct LNode//定义一个链表的结构类型 。{Elemtype data; //结点的数据域 。struct LNode* next; //结点的指针域 。}LNode, * LinkList;int Init_List(LNode** L);//使用这个函数来初始化一个节点 。运行成功返回OK 。int Assign_List(LNode** L);//为结点数据域赋值,成功返回OK,此函数可按需改变 。void Get_List(LNode* p, Elemtype* e);//取结点p的数据域 。此函数可按需改变void Print_List(Elemtype e);//打印数据域内容 。int Creat_List(LinkList* Head);//使用这个函数来创造一个链表,运行成功返回OK.void Read_List(LinkList head);//遍历链表LNode* Search_List(LinkList head, Elemtype e);//在链表中查找第一个含有元素e的节点,并返回这个结点的地址 。int Insert_List(LinkList* Head, Elemtype e);//在第一个含有元素e的结点后面插入一个新的结点,成功返回OK 。int Delete_List(LinkList* Head, Elemtype e);//删除第一个含有元素e的节点 。void Destroy_List(LinkList* Head);//销毁整个链表 。#endif
在编写这个代码时,注释中有此函数可按需更改 。意思是更改这个函数 。可以对不同的数据域进行操作 。以便于快速的修改整个程序代码 。创造不同的链表的目的 。
下面是源文件“链表.c”,主要放了链表相关函数的定义 。
//蔚明//链表.c/*在编写这个代码时,注释中有此函数可按需更改 。意思是更改这个函数可以对不同的数据域进行操作 。以便于快速的修改整个程序代码 。创造不同的链表的目的 。*/#include "链表.h"int Init_List(LNode** L)//使用这个函数来初始化一个节点 。运行成功返回OK 。{if ((*L = (LNode*)malloc(sizeof(LNode))) == NULL) //为结点分配内存{puts("内存分配失败!!!");exit(1);}else return OK;}int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变{Elemtype data;puts("请为结点赋值");scanf("%d", &data);(*L)->data = https://tazarkount.com/read/data;return OK;}void Get_List(LNode* p, Elemtype* e)//取结点p的数据域 。{*e = p->data;}void Print_List(Elemtype e)//打印数据域内容 。此函数可按需改变{printf("%d", e);}int Creat_List(LinkList* Head) //使用这个函数来创造一个链表,运行成功返回OK.{puts("正在创建一个链表 。");//创建一个只有头结点的空链表 。if (Init_List(Head) != OK) exit(1);(*Head)->next = NULL;LNode* r = *Head; //尾指针初始化并指向头结点 。char over_symbol = '#'; //结束创建的标志 。while (1==1){//生成新的结点p 。LNode* p;if (Init_List(&p) != OK) exit(1); //初始化结点p 。Assign_List(&p); //为新结点数据域赋值 。p->next = NULL;r->next = p; //把新结点插到尾结点之后 。r = p; //尾指针指向新结点 。//如果没有getchar()吃掉回车,会直接跳过下面的scanf()函数 。puts("结束请输入:over,否则请按");getchar();//吃掉回车 。scanf("ove%c", &over_symbol); //当输入"over"时跳出循环 。getchar();//吃掉回车 。if (over_symbol == 'r') break;//判断是否满足结束条件 。}puts("创建成功");return OK;}void Read_List(LinkList head)//遍历链表{LNode* read = head->next; //定义一个读取数据域的指针 。Elemtype e; //定义一个保存数据域的变量//寻链向下进行操作 。while (read != NULL){Get_List(read, &e); //把结点数据域内容读到变量ePrint_List(e); //打印数据域内容read = read->next;}ENTER;}LNode* Search_List(LinkList head, Elemtype e) //在链表中查找第一个含有元素e的节点,并返回这个结点的地址 。没找到返回NULL 。{LNode* read = head->next; //定义一个读取数据域的指针 。while (read != NULL) //在到达尾结点之前一直循环 。{if (read->data =https://tazarkount.com/read/= e) return read; //如果找到就返回,否则寻链向下 。else read = read->next;}return NULL; //到了为结点还是没找到,返回NULL 。}int Insert_List(LinkList* Head, Elemtype e) //在第一个含有元素e的结点后面插入一个新的结点,成功返回OK.{puts("\n正在插入一个结点");LNode* read = (*Head)->next; //定义一个读取数据域的指针 。LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针 。LNode* temp = NULL;//查找需要操作的结点 。while (read != NULL) //在到达尾结点之前一直循环 。{if (read->data =https://tazarkount.com/read/= e) break; //如果找到就返回,否则寻链向下 。else{read = read->next;pre_read = pre_read->next;}}if (read == NULL) return FAIL; //到了为结点还是没找到,返回 。temp = read; //使用temp临时保存要 操作的结点LNode* p = NULL; //生成新的结点p 。if (Init_List(&p) != OK) exit(1); //初始化结点p 。Assign_List(&p); //为新结点数据域赋值 。p->next = temp; //将新结点与原结点后继相连 。pre_read->next = p;//把原结点的前驱的后继与新结点相连 。puts("插入成功");return OK;}int Delete_List(LinkList* Head, Elemtype e) //删除第一个含有元素e的节点,成功返回OK 。{puts("\n正在删除一个结点");LNode* read = (*Head)->next; //定义一个读取数据域的指针 。LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针 。LNode* temp = NULL;//查找需要操作的结点 。while (read != NULL) //在到达尾结点之前一直循环 。{if (read->data =https://tazarkount.com/read/= e) break; //如果找到就返回,否则寻链向下 。else{read = read->next;pre_read = pre_read->next;}}if (read == NULL) return FAIL; //到了为结点还是没找到,返回 。temp = read->next; //使用temp临时保存要 操作的结点的后继 。free(read); //释放结点的空间 。pre_read->next = temp; //把此结点前驱和后继相连 。puts("删除成功");return OK;}void Destroy_List(LinkList* Head) //销毁整个链表 。{puts("\n正在销毁一个链表");LNode* p = *Head; //定义一个访问链表结点的指针 。LNode* temp = p->next; //使用temp临时保存要操作的结点的后继 。//寻链向下,依次释放内存 。do {free(p); //释放结点的空间 。p = temp; //结点指向它的后继 。temp = p->next;}while (temp != NULL);puts("销毁成功");return;}
下面是源文件“main.c”,主函数在这个文件中,主要是测试链表有关函数的功能是否正常 。
//蔚明//main.c#include"链表.h"void main(){LinkList head = NULL;Elemtype e = 0;if (Creat_List(&head) != OK)//创建一个头结点为head的链表 。{puts("创建链表失败!!!");exit(1);}Read_List(head);//读取链表的数据域并打印 。puts("请输入那个结点前需要插入");scanf("%d", &e);if(Insert_List(&head, e) != OK)//在链表中指定的结点前插入元素e{puts("插入失败!!!");exit(1);}puts("插入后的链表");Read_List(head);//读取链表的数据域并打印 。puts("请输入那个结点需要删除");scanf("%d", &e);if (Delete_List(&head, e) != OK){puts("删除失败!!!");exit(1);}puts("删除后的链表");Read_List(head);//读取链表的数据域并打印 。Destroy_List(&head);//释放为这个链表申请的内存 。return;} 运行结果如下:

我把Elemtype修改成了一个有名字,年龄的结构体变量,然后略微修改了对变量赋值的函数以及查找该变量的条件判断语句,部分修改如下
//Elemtype由int变为如下typedef struct person{char name[10];int age;}Elemtype;//变量的赋值和呈现进行修改int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变{Elemtype data=https://tazarkount.com/read/{"",0};puts("请输入名字");scanf("%s", data.name);getchar();puts("请输入年龄");scanf("%d", &data.age);(*L)->data = https://tazarkount.com/read/data;return OK;}void Print_List(Elemtype e)//打印数据域内容 。此函数可按需改变{printf("姓名:%s,年龄:%d\n", e.name,e.age);}//略微修改插入和删除函数中的判断语句if (read->data =https://tazarkount.com/read/= e) break; //改为if (strcmp(read->data.name, e.name)==0) return read;//略微修改主函数中的赋值语句scanf("%d",&e);//改为 scanf("%s", e.name);e.age = 0; 得到的运行结果如下

我学C语言大概半年,编写代码的手段可能比较稚嫩,如果写的比较垃圾,欢迎前辈指正 。
【超详细注释 C语言实现链表的基本操作】创作不易,求点赞 。