微网站栏目设置,响应式网站好处,wordpress打开首页很慢,武夷山市建设局网站声明 这一篇文章我会从单链表的概念#xff0c;单链表的原理#xff0c;一直到通讯录项目单链表的实现#xff0c;再把单链表的专用题型系统的讲解一下#xff08;文章较长#xff09;。同时建议学习单链表之前可以学习一下顺序表#xff0c;作为知识铺垫顺序表#xf…声明 这一篇文章我会从单链表的概念单链表的原理一直到通讯录项目单链表的实现再把单链表的专用题型系统的讲解一下文章较长。同时建议学习单链表之前可以学习一下顺序表作为知识铺垫顺序表增删减改通讯录项目数据结构顺序表专用题型-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137484207 链表的分类 这里我们主要讲解的是不带头的单向不循环链表在题型解析里面我们会讲解带头单向循环链表。 什么是单链表 单链表是一种数据结构 它的数据元素在物理上不连续但在逻辑上是连续的通过指针链接实现数据元素的顺序。在单链表中每个节点包含数据区和指针区用于存放数据和指向下一个节点的地址。 单链表的实现 主要包括头插法、尾插法、头删、尾删、查找、插入、删除等操作。它广泛应用于企业常用的技术中如排序、查找、插入、删除等操作。相较于顺序表链表在内存开辟和元素插入删除方面具有优势但其访问效率相较较低需要从头节点开始依序通过每个节点的指针到达下一个节点1279。 单链表的创建 单链表的创建主要包括定义节点类型初始化头节点以及实现插入、删除、查找等基本操作。在插入、删除等操作中需要改变相邻节点的指针指向。完整的单链表实现需要考虑节点的分配、释放以及相邻节点指针的改变469。 另外单链表还可以进行排序运算常见的排序算法有冒泡排序、插入排序和选择排序等8。需要注意的是在排序算法中单链表的插入和删除操作相对复杂需要分配额外的内存空间来存储临时节点8。 链表的概念 概念 链表是一种物理存储结构上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表 中的指针链接次序实现的。 链表的结构跟火⻋⻋厢相似淡季时⻋次的⻋厢会相应减少旺季时⻋次的⻋厢会额外增加几节。只需要将火⻋里的某节⻋厢去掉加上不会影响其他⻋厢每节⻋厢都是独立存在的。 ⻋厢是独立存在的且每节⻋厢都有⻋⻔。想象一下这样的场景假设每节⻋厢的⻋⻔都是锁上的状态需要不同的钥匙才能解锁每次只能携带一把钥匙的情况下如何从⻋头走到⻋尾 最简单的做法每节⻋厢里都放一把下一节⻋厢的钥匙 简单的说就是顺序表需要循环找到数值之后进行增删减改单链表大多数时候可以不用循环直接更换节点就像火车车厢一样。 单链表的结构 在结构上面 顺序表是线性的。 单链表在逻辑上是线性的在物理结构上不一定是线性的。 顺序表的原理 可以看到不管是物理上还是逻辑上都是顺序下去的也就是线性的逻辑的 顺序表实现通讯录我们发现很简单 单链表的使用和删除原理 车头也是车厢 每个车厢都是独立存在的 当我需要的时候增加车厢就可以因为每个车厢都是独立的 链表是由一个一个节点组成火车是由一节一节车厢组成 也就是我们需要数据就申请空间就可以 链表里面节点和结点是一样的 如何存储的这里可以发现逻辑上是线性的但是物理不是 从而 链表是由节点组成那么节点是由什么组成的 这里我们可以发现这里我们定义一个指针存贮一个地址 也就是由数据和指针组成的1 也就是指向下一个节点的地址从而找到我们需要的数据 从而我们得出结论我们需要定义链表也就是我们定义节点的结构 定义链表的节点的结构 Node-表示节点 sinfle-单身 List-链表 所以单链表也就是可以定义为 所以下一个我们就可以定义为下一个节点的指针 下一个节点的地址就是结构体指针指向的是下一个节点的指针 next要存储下一个节点的指针 单链表的链接原理 C语言-malloc申请函数free释放函数-CSDN博客 首先我们可以看到单链表在空间是用malloc当然你用realloc也是可以的 但是到连接的时候你会发现不一定是按照顺序进行连接的所以逻辑上我们可以知道这个是线性的但是物理上不一定是线性的 单链表实现通讯录图解这里的代码可以不用看懂我是为了画图进行铺垫结构体-前置声明-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137497379这里涉及到前置声明附带连接 #pragma once
#define NAME_MAX 20//姓名
#define SEX_MAX 20//性别
#define TEL_MAX 20//电话
#define ADDR_MAX 100//地址//前置声明
typedef struct SListNode Address_Book;//用户数据
typedef struct PersonInfo
{char name[NAME_MAX];//姓名char sex[SEX_MAX];//性别int age;//年龄char tel[TEL_MAX];//电码char addr[ADDR_MAX];//地址
}PeoInfo;
typedef PeoInfo SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode; 也就是我们删除一个节点这个节点的内容直接全部删除了 单链表的实现增删减改 创建文件 和顺序表一样也是创建三个文件分别是头文件实现文件测试文件 这里我们主要是先学习链表通讯录自然而然也就会了 创建链表 链表的实现 //SList.h文件
#pragma once//自定义类型
typedef int SLTDataType;//自定义类型
typedef PeoInfo SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode; 第一个是存储的数据 第二个是指针指向节点的指针 一个数据结构不一定只是存储一个类型 节点的结构已完成 申请空间 链表本质还是需要占用空间的所以我们创建好链表之后不能直接进行使用因为没有空间所以我们需要申请空间 //SList.h文件
//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x);//SList.c文件
#includeSList.h
//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x)
{SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(newnode:error);exit(1);}newnode-data x;newnode-next NULL;return newnode;
} 链表不会涉及增容所以用malloc同时只要的开辟空间就有可能失败所以我们需要进行一个判断是不是创建空间失败。 没有失败的情况下 newnode-data x;//当前节点的数值赋值我需要存储的数值 newnode-next NULL;//下一个节点就是null因为我们的一个节点一个节点创建的。 测试申请空间 //test.c文件
void sl01()
{printf(测试1\n);//链表的申请空间printf(\n申请空间\n);SLTNode* s1 SLBuyNode(4);SLTNode* s2 SLBuyNode(5);SLTNode* s3 SLBuyNode(6);s1-next s2;s2-next s3;s3-next NULL;//打印链表SLTPrint(s1);//销毁链表SListDesTroy(s1);printf(\n\n\n);}
int main()
{s101();return 0;
} 当然你也可以这样但是既然我们定义了那么我们就可以直接调用 这个时候我们定义一个节点传过去防止改变代码 打印出来看看效果 打印单链表 打印是很简单的 头文件 //SList.h文件
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestdlib.h
#includestring.h
#includeAddress_Book.h//通讯录的前置声明
#pragma once//自定义类型
//typedef int SLTDataType;//自定义类型
typedef PeoInfo SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//链表的初始化
void SLInfo(SLTNode** pphead);//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x);//打印链表
void SLTPrint(SLTNode* pphead); 实现文件 //SList.c文件
//打印链表
void SLTPrint(SLTNode* pphead)
{assert(pphead);SLTNode* pure pphead;while (pure){printf(%d-, pure-data);pure pure-next;}printf(NULL\n);
} 然后我们在test.c里面进行测试申请空间是否成功发现没有问题 //test.c文件
void sl01()
{printf(测试1\n);//链表的申请空间printf(\n申请空间\n);SLTNode* s1 SLBuyNode(4);SLTNode* s2 SLBuyNode(5);SLTNode* s3 SLBuyNode(6);s1-next s2;s2-next s3;s3-next NULL;//打印链表SLTPrint(s1);//销毁链表SListDesTroy(s1);printf(\n\n\n);}
int main()
{s101();return 0;
} 接下来我的的代码里面不会全部包含头文件实现文件和测试文件了大部分只会直接包含头文件方便观看在最后的时候会给出代码的总结 链表的初始化 //链表的初始化
void SLInfo(SLTNode** pphead)
{// 创建头结点SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(malloc failed);exit(1);}*pphead newnode;// 初始化头结点的成员(*pphead)-next NULL;
} 初始化的目的 初始化基本上是每一个代码使用都需要进行初始化养成良好代码风格 单链表初始化的目的是为了创建一个空的链表数据结构以便后续可以在这个基础上进行各种操作如插入、删除、查找等。初始化通常包括以下几个步骤 1. 分配内存空间为链表的头部创建一个节点这个节点将包含指向链表第一个元素的指针。如果链表为空这个指针将为NULL。 2. 设置初始状态将链表的各个属性和指针初始化为默认状态比如长度为0头节点指向NULL等。 3. 确保可使用性初始化之后链表应该处于一个可以进行操作的状态这意味着它可以安全地接收新的元素同时用户可以检查链表是否为空等。 初始化单链表的目的是为了让程序有一个干净的起点并能够按照预定的方式进行扩展。这样做可以避免在使用链表之前对链表结构进行不必要的猜测或错误操作确保数据结构的一致性和稳定性。 初始化的代码解释 简单是说就是初始化的目的往往是创建一个头结点头结点和第一个节点是不一样的头结点是null的节点图解里面1是第一个节点是有实际数值的但是头结点是没有实际数值的也就是哨兵位的意思哨兵位顾名思义也就是放哨的地方而你进行尾插的时候第一个数值的插入往往都是从头结点往后进行插入 下面我们会讲解头结点存在的目的。 链表的销毁 这里先进行链表的销毁代码的实现因为这个代码的书写比较方便。 //链表的销毁
void SListDesTroy(SLTNode** pphead)
{assert(pphead *pphead);SLTNode* pure *pphead;while (pure){//存储节点不能直接进行销毁如果直接是purepure-next;那么就会导致找不到下一个节点了SLTNode* next pure-next;//销毁当前节点free(pure);//指向下一个节点这里不需要指向null因为销毁了你不使用了pure next;}//头结点指向空*pphead NULL;printf(销毁成功\n);
} 链表的销毁和申请节点正好是对应的创建空间就需要进行销毁只要是不使用。不然会造成内存泄露从而使内存被占用。 这里销毁的时候我们需要先存储下一个节点然后销毁当前节点然后让节点向下继续最后全部销毁之后需要把头结点置为空节点。 链表的尾插 尾插就是需要先找到尾结点然后将尾结点和新节点进行连接 走到为null的地方 也就是ptail指针指向的下一个节点不为空 当跳出循环的时候 指向的就是尾结点 每次插入节点我们都需要申请空间所以可以看到我们代码进行尾插的时候需要申请空间 尾插处理空链表和非空链表的时候 空链表,不是空链表就去找尾结点 //链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode SLBuyNode(x);//申请空间防止为空if (*pphead NULL)//判断我们进行尾插的时候节点是不是一个没有进行判断{*pphead newnode;}else{SLTNode* pure *pphead;//这里的循环条件必须是pure-next 因为只是pure的情况下循环里面就会产生到最后一个指针的情况下对null解应用while (pure-next){pure pure-next;}pure-next newnode;}
} 链表的头插 链表的头插位空和不为空的时候是不一样的 当为空链表的时候 当前的节点就是第一个节点。 链表不为空的情况下 指向的节点我们可以直接让新的节点的下一个节点等于当前的第一个节点 然后把新节点变成第一个节点也就是newnode //链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead NULL){*pphead newnode;} else{newnode-next *pphead;*pphead newnode;}
} 链表的尾删 这里加一个断言不能为null如果等于null说明空没的删除 这里我们是不能直接释放的如果直接释放的情况下我们需要把最后一个节点置为空指针也就是我们不仅需要找到尾结点还需要 找到最后一个节点的前一个节点 代码这里我们依旧需要判断是不是只有一个节点或者是不是为null所以我们采取断言和判断要是下一个节点是null那么说明当前的节点就是最后一个节点所以我们直接释放就可以不需要进行循环了 如果不是只有一个节点的情况下此时我们为了不让第一个节点进行移动所以我们需要创建一个节点和头结点指向的空间一样向后移动当这个节点下一个节点为null的时候也就是说明此时找到最后一个节点。此时可以对节点进行删除。 //链表的尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead *pphead);//当只有一个节点的时候我们为了防止释放之后变成野指针因为释放的当前位置等于null之后free(nodetile);nodetile NULL;//你继续进行下一个节点的置为nullpure-next NULL;会导致越界访问//所以我们进行一个判断也就只有一个节点的时候if ((*pphead)-next NULL){free(*pphead);*pphead NULL;}else{SLTNode* nodetile *pphead;SLTNode* pure *pphead;while (nodetile-next){pure nodetile;nodetile nodetile-next;}free(nodetile);nodetile NULL;pure-next NULL;}
} 链表的头删 链表的头删还是很简单的只需要知道第一个节点然后就知道第一个结点的下一个节点我们让第一个节的下一节点也就是第二节点等于头结点就可以。然后同时销毁第一个节点。 //链表的头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead *pphead);//这里不需要判断是不是只有一个头结点因为删除时候下一个节点也只是NULL我们只是把NULL赋值到新的头结点没有越界访问SLTNode* newhead (*pphead)-next;free(*pphead);*pphead NULL;*pphead newhead;
} 链表的指定位置的删除 在链表中删除一个节点时我们需要找到pos节点的前一个节点以便将前一个节点的指针指向pos节点的下一个节点从而切断与pos节点的链接使其从链表中脱离。因此循环的条件应该是purc-next ! pos即继续遍历直到找到最后一个节点其next指针指向pos节点。 如果将循环条件改为purc ! pos那么当purc等于pos时循环将继续这将导致访问非法的内存因为pos已经是要删除的节点它的next指针可能已经改变或者将被释放这会导致程序崩溃。 所以循环中的purc-next ! pos是为了确保我们没有试图访问已经被删除或者即将被删除的节点而是找到链表中最后一个指向pos的节点。找到这样的节点后我们将它的next指针改为指向pos的下一个节点从而实现从链表中删除pos节点。最后释放pos节点占用的内存将其设置为NULL以避免产生野指针。 //链表的指定位置的删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead pphead);if (*pphead pos){SLTNode* newhead (*pphead)-next;free(*pphead);*pphead newhead;}else{//需要删除的节点SLTNode* del *pphead;while (del-next ! pos){del del-next;}del-next pos-next;free(pos);pos NULL;}
} 链表的查找 链表的查找这里的目的不仅仅是进行链表的查找还有就是我们进行查找的时候我们需要先找得到才能进行删除。所以我们才需要进行查找 这里查找到之后返回的是节点的地址之后我们进行数值的操作的时候可以直接查找到之后进行删除 //链表的查找
SLTNode* SLTFind(SLTNode* pphead, SLTDataType x)
{assert(pphead);SLTNode* pure pphead;while (pure){if (pure-data x){return pure;//这里不能返回pure-data只能返回pure因为这里是返回一个节点不是一个整数}//其实这里最后一步是产生越界访问的但是其实你不使用的也没事情pure pure-next;}return -1;
} 删除指定位置之后的数据版本1 //删除指定位置之后的数据
void SLTEraseAfterPlus(SLTNode* pos)
{assert(pos);SLTNode* del pos-next;if (del-next NULL){perror(null:);}pos-next del-next;free(del);del NULL;
}这里直接删除但是是有要求的 我们往往需要先调用查找函数找到数值所在位置然后传递参数所以要是在通讯录里面进行实现的时候往往需要重复调用但是只是在单链表里面效率当然会更快。 //指定位置之后删除printf(\n指定位置之后删除\n);SLTNode* ret1 SLTFind(s1, 66);SLTEraseAfter(s1, ret1);SLTPrint(s1); 这里解释一下ret1不能直接传递66因为我们传递的是节点类型的参数不是整数类型的形参是不接收的。所以必须先进行查找。 删除指定位置之后的数据版本2 //删除指定位置之后的节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pphead *pphead);SLTNode* del *pphead;SLTNode* next pos-next;while (del ! pos){del del-next;}if (del-next NULL){perror(null:);}pos-next next-next;free(next);next NULL;
} 这里的意思是进行循环找到需要删除之前的节点也就是如果子啊通讯录里面直接调用是很方便的。所以我特地写一个版本2进行对比使用。 指定位置之前插入 指定位置之前进行插入和之后插入的关键其实都是找到指定位置pospos也就是查找的结果查找到之后传递参数到函数实参里面实参传递到形参进行计算。 这里依旧是需要判断是不是null是的话进行头插 不是的话创建变量进行移动创建变量的目的是不改变头结点因为我们是二级指针这里了改变头结点头结点就发生了变化。 然后我们让创建的变量循环寻找pos节点只要下一个节点指向的不是pos节点就可以我们在pos节点前进行插入我们需要pos节点和pos上一个节点。 我们找到之后这里很关键 我们不能直接让pure的下一个节点指向新节点这样会导致数据的丢失。 1所以我们需要先改变新节点的指向newnode 2链接到链表里面newnode-nextpos 3然后再把上一个节点指向新节点pure-next newnode;此时才是对的。 4但是你要是非得先指向新节点也可以你存储pos后面的节点然后进行改变也是可以的。 //指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead pos){SLTPushFront(pphead, x);}else{SLTNode* pure *pphead;while (pure-next ! pos){pure pure-next;}newnode-next pos;pure-next newnode;}
} 指定位置之后插入 然后我们让创建的变量循环寻找pos节点或者用查找函数进行查找但是没必要我们直接书写了。 找到之后这里很关键 我们不能直接让pos的下一个节点指向新节点这样会导致数据的丢失所以我们需要先改变新节点的指向链接到链表里面然后再把上一个节点指向新节点此时才是对的。 但是你要是非得先指向新节点也可以你存储pos后面的节点然后进行改变也是可以的。 //指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode SLBuyNode(x);newnode-next pos-next;pos-next newnode;
} 单链表补充 头节点存在的目的 在单链表的使用中头结点Header Node是一个常用的概念特别是在进行链表操作时。头结点不是数据域中实际存储的数据节点而是作为链表操作的辅助节点它包含对第一个实际数据节点的引用。以下是一些在使用单链表时可能需要头结点的情况 1. **简化插入操作**头结点可以简化插入操作特别是在插入节点到链表的头部时。不需要修改已有节点的指针只需要改变头结点的指针即可。 2. **统一插入和删除操作**头结点使得对链表的插入和删除操作更加统一。无论是插入还是删除都可以通过头结点来定位到操作位置的前一个节点而不需要关心链表的具体内容。 3. **处理空链表**在处理空链表时头结点非常有用。例如当检查链表是否为空时只需要检查头结点的指针是否为NULL而不需要遍历整个链表。 4. **保护头结点**头结点可以作为链表的防护措施当链表为空时头结点可以防止访问非法的内存地址。 5. **方便遍历链表**头结点可以作为遍历链表的起点从头结点开始可以逐一访问链表中的每个节点。 6. **实现双向链表**在实现双向链表时头结点可以同时存储向前和向后的指针这样可以更方便地实现双向遍历和操作。 总结来说头结点在单链表的使用中提供了许多便利它使得链表的操作更加简洁、统一并且更加安全和高效。因此在实现和操作单链表时头结点是一个非常有用的工具。 当然很多时候你也是可以不进行初始化的。但是初始化之后对于代码是书写可以更方便。 举例哨兵位的申请和头结点的申请的区别 在C语言中哨兵位节点和申请空间的实现代码主要区别在于它们各自的应用场景和目的。 1. 哨兵位节点 哨兵位节点通常用于解决链表中的循环链表问题。在循环链表中我们需要一个特殊的节点来标记链表的末尾这个特殊的节点就是哨兵位节点。哨兵位节点的实现代码通常包括创建一个哨兵位节点并将其指向链表的头节点。 以下是一个简单的哨兵位节点的实现代码 #include stdio.h
#include stdlib.h
typedef struct Node {int data;struct Node* next;
} Node;
Node* createSentryNode(Node* head) {Node* sentry (Node*)malloc(sizeof(Node));sentry-data -1; // 哨兵位节点的数据域通常设置为一个特殊值如-1sentry-next head;return sentry;
}
int main() {Node* head (Node*)malloc(sizeof(Node));head-data 1;head-next (Node*)malloc(sizeof(Node));head-next-data 2;head-next-next (Node*)malloc(sizeof(Node));head-next-next-data 3;head-next-next-next head; // 创建循环链表Node* sentry createSentryNode(head);// 接下来可以使用sentry节点进行循环链表的操作如查找、删除等return 0;
}2. 申请空间的申请代码 申请空间的申请代码通常用于动态分配内存空间例如在程序运行过程中创建动态数据结构。在C语言中我们通常使用malloc()函数来申请内存空间。 以下是一个简单的申请空间的实现代码 #include stdio.h
#include stdlib.h
int main() {int* ptr (int*)malloc(sizeof(int)); // 申请一个整数大小的内存空间if (ptr NULL) {printf(内存申请失败\n);return 1;}*ptr 42; // 在申请的内存空间中存储一个整数printf(存储的整数 %d\n, *ptr);free(ptr); // 释放申请的内存空间return 0;
}单链表使用里面为什么是二级指针 这里很多人就会疑问为什么顺序表里面是一级指针单链表里面是二级指针。 这里我们专门列出来进行讲解。 因为传递的不是二级指针的话会导致传参之后形参改变实参不改变 你希望形参改变实参也改变就必须传递地址 简单的解释就是 1我们申请开辟了一个空间此时这个空间需要用指针指向这个空间 2然后我们需要调用这个空间进行增删减改的时候 3如果我们直接传递指针过去的话也就是会传递形参也就是传值调用然后我们的编译器会拷贝一份数值在新开辟的空间计算完毕之后返回你需要的数值。 4但是此时只是形参的数值完成了改变也就是*phead的数值计算完成因为是形参。 5我们需要的是newnode的空间的数值完成计算那么此时我们需要传递指针的地址也就是传递指针指向空间的地址也就是指针的地址。 6.此时也就来到了我们图解的**phead,所以需要对指针进行取地址 7此时我们对实参进行取地址了也就是指针的取地址那么形参我们接收的话是不是需要用二级指针。 8所以就产生了二级指针 9最后我们可以发现申请空间的时候和打印的时候是不需要二级指针的为什么很简单我们申请的空间本身是不涉及指针的只有申请结束我们需要用指针指向这个节点形成逻辑的线性表。 10在打印的时候我们需要的是不需要改变数值所以传递形参也就是一级指针指向链表的指针。 11所以这里也解释了为什么有时候需要二级指针有时候需要一级指针不需要改变数值的时候我们只需要一级指针传参就可以了需要改变数值的时候我们需要取出指针的地址我们需要的是形参的改变影响实参。 12要是不知道什么是形参什么是实参的小伙伴可以看一下函数的知识。 printf(测试2\n);SLTNode* s1 NULL;//尾插// 首先链表需要指针指向链表// 我们传递是时候是需要形参变化实参也发生变化的// 那么如果只是传递一个指针的情况下这里实际你传递的是指针的数值也就会导致形参变化实参不变化// 但是我们需要的是形参变化实参也变化所以此时我们需要传递指针的地址也就是s1并且用二级指针进行接收// 取的是指针指向的空间的地址因为只是传参传递的是s1的情况下printf(\n尾插\n);SLTPushBack(s1, 11);SLTPushBack(s1, 22);SLTPushBack(s1, 33);SLTPrint(s1); 单链表代码的总结 SList.h文件 //SList.h文件
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestdlib.h
#includestring.h
#includeAddress_Book.h//通讯录的前置声明
#pragma once//自定义类型
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//链表的初始化
void SLInfo(SLTNode** pphead);//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x);//打印链表
void SLTPrint(SLTNode* pphead);//链表的销毁
void SListDesTroy(SLTNode** pphead);//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//链表的尾删
void SLTPopBack(SLTNode** pphead);//链表的头删
void SLTPopFront(SLTNode** pphead);//链表的查找
SLTNode* SLTFind(SLTNode* pphead, SLTDataType x);//链表的指定位置的删除
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除指定位置之后的数据1
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的数据2
void SLTEraseAfterPlus(SLTNode* pos);//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x); SList.c文件 //SList.c文件#includeSList.h
//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x)
{SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(newnode:error);exit(1);}newnode-data x;newnode-next NULL;return newnode;
}//链表的初始化
void SLInfo(SLTNode** pphead)
{// 创建头结点SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(malloc failed);exit(1);}*pphead newnode;// 初始化头结点的成员(*pphead)-next NULL;
}//打印链表
void SLTPrint(SLTNode* pphead)
{assert(pphead);SLTNode* pure pphead;while (pure){printf(%d-, pure-data);pure pure-next;}printf(NULL\n);
}//链表的销毁
void SListDesTroy(SLTNode** pphead)
{assert(pphead *pphead);SLTNode* pure *pphead;while (pure){//存储节点不能直接进行销毁如果直接是purepure-next;那么就会导致找不到下一个节点了SLTNode* next pure-next;//销毁当前节点free(pure);//指向下一个节点这里不需要指向null因为销毁了你不使用了pure next;}//头结点指向空*pphead NULL;printf(销毁成功\n);
}//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode SLBuyNode(x);if (*pphead NULL){*pphead newnode;}else{SLTNode* pure *pphead;//这里的循环条件必须是pure-next 因为只是pure的情况下循环里面就会产生到最后一个指针的情况下对null解应用while (pure-next){pure pure-next;}pure-next newnode;}
}//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead NULL){*pphead newnode;} else{newnode-next *pphead;*pphead newnode;}
}//链表的尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead *pphead);//当只有一个节点的时候我们为了防止释放之后变成野指针因为释放的当前位置等于null之后free(nodetile);nodetile NULL;//你继续进行下一个节点的置为nullpure-next NULL;会导致越界访问//所以我们进行一个判断也就只有一个节点的时候if ((*pphead)-next NULL){free(*pphead);*pphead NULL;}else{SLTNode* nodetile *pphead;SLTNode* pure *pphead;while (nodetile-next){pure nodetile;nodetile nodetile-next;}free(nodetile);nodetile NULL;pure-next NULL;}
}//链表的头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead *pphead);//这里不需要判断是不是只有一个头结点因为删除时候下一个节点也只是NULL我们只是把NULL赋值到新的头结点没有越界访问SLTNode* newhead (*pphead)-next;free(*pphead);*pphead NULL;*pphead newhead;
}//链表的查找
SLTNode* SLTFind(SLTNode* pphead, SLTDataType x)
{assert(pphead);SLTNode* pure pphead;while (pure){if (pure-data x){return pure;
//这里不能返回pure-data只能返回pure因为这里是返回一个节点不是一个整数}//其实这里最后一步是产生越界访问的但是其实你不使用的也没事情pure pure-next;}return -1;
}//链表的指定位置的删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead pphead);if (*pphead pos){SLTNode* newhead (*pphead)-next;free(*pphead);*pphead newhead;}else{//需要删除的节点SLTNode* del *pphead;while (del-next ! pos){del del-next;}del-next pos-next;free(pos);pos NULL;}
}//删除指定位置之后的节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pphead *pphead);SLTNode* del *pphead;SLTNode* next pos-next;while (del ! pos){del del-next;}if (del-next NULL){perror(null:);}pos-next next-next;free(next);next NULL;
}//删除指定位置之后的数据
void SLTEraseAfterPlus(SLTNode* pos)
{assert(pos);SLTNode* del pos-next;if (del-next NULL){perror(null:);}pos-next del-next;free(del);del NULL;
}//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead pos){SLTPushFront(pphead, x);}else{SLTNode* pure *pphead;while (pure-next ! pos){pure pure-next;}newnode-next pos;pure-next newnode;}
}//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode SLBuyNode(x);newnode-next pos-next;pos-next newnode;
} test.c文件 test.c文件
#includeSList.hvoid sl01()
{printf(测试1\n);//链表的申请空间printf(\n申请空间\n);SLTNode* s1 SLBuyNode(4);SLTNode* s2 SLBuyNode(5);SLTNode* s3 SLBuyNode(6);s1-next s2;s2-next s3;s3-next NULL;//打印链表SLTPrint(s1);//销毁链表SListDesTroy(s1);printf(\n\n\n);}void s102()
{printf(测试2\n);SLTNode* s1 NULL;//尾插// 首先链表需要指针指向链表// 我们传递是时候是需要形参变化实参也发生变化的// 那么如果只是传递一个指针的情况下这里实际你传递的是指针的数值也就会导致形参变化实参不变化// 但是我们需要的是形参变化实参也变化所以此时我们需要传递指针的地址也就是s1并且用二级指针进行接收// 取的是指针指向的空间的地址因为只是传参传递的是s1的情况下printf(\n尾插\n);SLTPushBack(s1, 11);SLTPushBack(s1, 22);SLTPushBack(s1, 33);SLTPushBack(s1, 44);SLTPushBack(s1, 55);SLTPushBack(s1, 66);SLTPushBack(s1, 77);SLTPushBack(s1, 88);SLTPrint(s1);//头插printf(\n头插\n);SLTPushFront(s1, 0);SLTPrint(s1);//删除链表尾删printf(\n删除链表尾删\n);SLTPopBack(s1);SLTPrint(s1);//头删printf(\n删除链表头删\n);SLTPopFront(s1);SLTPrint(s1);//链表的查找printf(\n查找\n);SLTNode* find SLTFind(s1, 11);if (find -1){printf(没找到\n);}else{printf(找到了:%d\n, s1-data);//这里不能是find因为find返回的是一个地址如果返回的是整数那么下面进行计算的时候就依旧变成了传值调用了}//指定位置的删除printf(\n指定位置的删除\n);SLTNode* ret SLTFind(s1, 11);SLTErase(s1, ret);//这里之所以传递的是取地址s1和ret因为传递的是地址是相同类型的SLTNode* pos如果传递是数值也就是如果返回的是数值那么会导致依旧变成传值调用SLTPrint(s1);//指定位置之后删除printf(\n指定位置之后删除\n);SLTNode* ret1 SLTFind(s1, 66);SLTEraseAfter(s1, ret1);SLTPrint(s1); SLTNode* ret2 SLTFind(s1, 55);SLTEraseAfterPlus(ret2);SLTPrint(s1);//指定位置之前插入printf(\n指定位置之前插入\n);SLTInsert(s1, NULL, 99);SLTPrint(s1);SLTInsert(s1, ret2, 99);SLTPrint(s1);SLTNode* ret3 SLTFind(s1, 22);SLTInsert(s1, ret3, 99);SLTPrint(s1);//指定位置之后插入printf(\n指定位置之后插入\n);SLTNode* ret4 SLTFind(s1, 22);SLTInsertAfter(ret4, 100);SLTPrint(s1);//销毁printf(\n销毁\n);SListDesTroy(s1);printf(\n\n\n);}int main()
{sl01();s102();return 0;
} 到这里我们单链表的知识点算是结束了下面我们进行实践 通讯录 声明 这里首先进行声明这里的通讯录的实现是基于单链表进行实现的 自定义数据 自定义数据之后我们可以把之前定义的int类型进行替换 //前置声明
typedef struct SListNode Address_Book;//用户数据
typedef struct PersonInfo
{char name[NAME_MAX];//姓名char sex[SEX_MAX];//性别int age;//年龄char tel[TEL_MAX];//电码char addr[ADDR_MAX];//地址
}PeoInfo; 结构体-前置声明-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137497379前置声明不理解的可以去看看 添加通讯录数据 这里我们调用链表的尾插函数进行添加通讯录项目当然我们需要输入最后进行插入就可以 //添加通讯录数据
void AddContact(Address_Book** con)
{PeoInfo info;printf(请输入姓名\n);scanf(%s, info.name);printf(请输入性别\n);scanf(%s, info.sex);printf(请输入年龄\n);scanf(%d, info.age);printf(请输入电话\n);scanf(%s, info.tel);printf(请输入地址\n);scanf(%s, info.addr);SLTPushBack(con, info);printf(添加成功\n\n);
} 删除通讯录数据 只要是涉及到删除我们肯定需要进行查找需要找到是否有这个名字才能进行删除。所以我们需要调用查找函数。查找函数的实现下面我们会进行实现。 //删除通讯录数据
void DelContact(Address_Book** con)
{assert(con *con);char name[NAME_MAX];//姓名printf(请输入你需要删除的姓名:\n);scanf(%s, name);Address_Book* ret FindByName(*con, name);//这里需要传递if (ret NULL){printf(没有找到这个人\n\n);exit(1);}else{//这里没有排除删除头结点写到一半我们发现其实我们可以直接把指定位置删除的数据拿来//Address_Book* pure *con;//while (pure-next ! ret)//{// pure pure-next;//}//pure-next ret-next;SLTErase(con, ret);}
} 展示通讯录数据 这里打印一个表头创建一个变量变量进行移动。 //展示通讯录数据
void ShowContact(Address_Book* con)
{printf(%s %s %s %s %s\n, 姓名, 性别, 年龄, 电话, 地址);Address_Book* pure con;while (pure){printf(%s %s %d %s %s\n,pure-data.name,pure-data.sex,pure-data.age,pure-data.tel,pure-data.addr);pure pure-next;}
} 查找通讯录数据 涉及到删除肯定需要查找我们查找可以是查找姓名或者电话或者性别等等这里我们采取的是姓名的查找 //查找通讯录数据(查找名字)
Address_Book* FindByName(Address_Book* con, char name[])
{assert(con);Address_Book* pure con;while (pure){if (0 strcmp(pure-data.name, name)){return pure;//返回当前的节点}pure pure-next;}return NULL;
} 修改通讯录数据 修改通讯录其实就是直接在原来的函数基础上进行覆盖当然还是进行查找找到才能修改。找到后会直接返回节点我们根据节点直接对其进行数值的覆盖。 //修改通讯录数据
void ModifyContact(Address_Book** con)
{assert(*con con);char name[NAME_MAX];//姓名printf(请输入你需要修改的姓名:\n);scanf(%s, name);// 这里需要传递指针因为接受的是二级指针我们需要形参的改变影响实参传递来的是指向链表的指针的地址// 这个指针指向链表的空间所以我们要修改通讯录的内容需要把指向这个链表的指针传递过去// 指针找到这个名字返回值不是空说明找到了返回的是节点的地址// 最后我们直接对节点内存空间进行修改因为我们这里申请空间是节点指向的下一个的内存空间// 所以我们需要每次进入到节点的内存块里面进行内存的修改Address_Book* ret FindByName(*con, name);if (ret NULL){printf(没有找到这个人\n\n);exit(1);}printf(请输入姓名\n);scanf(%s, ret-data.name);printf(请输入性别\n);scanf(%s, ret-data.sex);printf(请输入年龄\n);scanf(%d, ret-data.age);printf(请输入电话\n);scanf(%s, ret-data.tel);printf(请输入地址\n);scanf(%s, ret-data.addr);printf(修改成功\n\n);
} 写入到文件里面 C语言-文件操作函数基础fgetc读字符fputc写字符fgets读文本fputs写文本fclose关闭文件fopen打开文件-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137128099文件其实就是函数的理解这里附带两个链接。讲解的还是很透彻的 //写入到文件里面
void LoadContact(Address_Book** con)
{assert(*con con);FILE* ps fopen(Address_Book.txt, w);if (ps NULL){perror(fopen:book:);exit(1);}Address_Book* newnode *con;while (newnode! NULL){fgets(con, 1, ps);fprintf(ps, %s %s %d %s %s\n, newnode-data.name,newnode-data.sex,newnode-data.age,newnode-data.tel,newnode-data.addr);newnode newnode-next;}fclose(ps);psNULL;printf(成功写到文件里面\n);
} C语言-文件操作函数进阶printfscanfsscanfsprintffprintffscanffwritefreadfseekftellrewindfeof-CSDN博客https://blog.csdn.net/Jason_from_China/article/details/137155626 通讯录代码的总结 Address_Book.h文件 //Address_Book.h文件
#pragma once
#define NAME_MAX 20//姓名
#define SEX_MAX 20//性别
#define TEL_MAX 20//电话
#define ADDR_MAX 100//地址//前置声明
typedef struct SListNode Address_Book;//用户数据
typedef struct PersonInfo
{char name[NAME_MAX];//姓名char sex[SEX_MAX];//性别int age;//年龄char tel[TEL_MAX];//电码char addr[ADDR_MAX];//地址
}PeoInfo;
//通讯录的初始化
void InitContact(Address_Book** con);
//添加通讯录数据
void AddContact(Address_Book** con);
//删除通讯录数据
void DelContact(Address_Book** con);
//展示通讯录数据
void ShowContact(Address_Book* con);
//查找通讯录数据
void FindContact(Address_Book* con);
//修改通讯录数据
void ModifyContact(Address_Book** con);
//销毁通讯录数据
void DestroyContact(Address_Book** con);
//写入到文件里面
void LoadContact(Address_Book** con); Address_Book.c文件 //Address_Book.c文件
#includeSList.h
#includeAddress_Book.h
//添加通讯录数据
void AddContact(Address_Book** con)
{PeoInfo info;printf(请输入姓名\n);scanf(%s, info.name);printf(请输入性别\n);scanf(%s, info.sex);printf(请输入年龄\n);scanf(%d, info.age);printf(请输入电话\n);scanf(%s, info.tel);printf(请输入地址\n);scanf(%s, info.addr);SLTPushBack(con, info);printf(添加成功\n\n);
}
//展示通讯录数据
void ShowContact(Address_Book* con)
{printf(%s %s %s %s %s\n, 姓名, 性别, 年龄, 电话, 地址);Address_Book* pure con;while (pure){printf(%s %s %d %s %s\n,pure-data.name,pure-data.sex,pure-data.age,pure-data.tel,pure-data.addr);pure pure-next;}
}//查找通讯录数据(查找名字)
Address_Book* FindByName(Address_Book* con, char name[])
{assert(con);Address_Book* pure con;while (pure){if (0 strcmp(pure-data.name, name)){return pure;//返回当前的节点}pure pure-next;}return NULL;
}
//删除通讯录数据
void DelContact(Address_Book** con)
{assert(con *con);char name[NAME_MAX];//姓名printf(请输入你需要删除的姓名:\n);scanf(%s, name);Address_Book* ret FindByName(*con, name);//这里需要传递if (ret NULL){printf(没有找到这个人\n\n);exit(1);}else{//这里没有排除删除头结点写到一半我们发现其实我们可以直接把指定位置删除的数据拿来//Address_Book* pure *con;//while (pure-next ! ret)//{// pure pure-next;//}//pure-next ret-next;SLTErase(con, ret);}
}//查找通讯录数据(查找名字)
void FindContact(Address_Book* con)
{assert(con);char name[NAME_MAX];//姓名printf(请输入你需要查找的姓名:\n);scanf(%s, name);Address_Book* ret FindByName(con, name);//这里需要传递if (ret NULL){printf(没有找到这个人\n\n);exit(1);}else{printf(%s %s %d %s %s\n,con-data.name,con-data.sex,con-data.age,con-data.tel,con-data.addr);printf(查找成功\n\n);}
}
//修改通讯录数据
void ModifyContact(Address_Book** con)
{assert(*con con);char name[NAME_MAX];//姓名printf(请输入你需要修改的姓名:\n);scanf(%s, name);// 这里需要传递指针因为接受的是二级指针我们需要形参的改变影响实参传递来的是指向链表的指针的地址// 这个指针指向链表的空间所以我们要修改通讯录的内容需要把指向这个链表的指针传递过去// 指针找到这个名字返回值不是空说明找到了返回的是节点的地址// 最后我们直接对节点内存空间进行修改因为我们这里申请空间是节点指向的下一个的内存空间// 所以我们需要每次进入到节点的内存块里面进行内存的修改Address_Book* ret FindByName(*con, name);if (ret NULL){printf(没有找到这个人\n\n);exit(1);}printf(请输入姓名\n);scanf(%s, ret-data.name);printf(请输入性别\n);scanf(%s, ret-data.sex);printf(请输入年龄\n);scanf(%d, ret-data.age);printf(请输入电话\n);scanf(%s, ret-data.tel);printf(请输入地址\n);scanf(%s, ret-data.addr);printf(修改成功\n\n);
}
//写入到文件里面
void LoadContact(Address_Book** con)
{assert(*con con);FILE* ps fopen(Address_Book.txt, w);if (ps NULL){perror(fopen:book:);exit(1);}Address_Book* newnode *con;while (newnode! NULL){fgets(con, 1, ps);fprintf(ps, %s %s %d %s %s\n, newnode-data.name,newnode-data.sex,newnode-data.age,newnode-data.tel,newnode-data.addr);newnode newnode-next;}fclose(ps);psNULL;printf(成功写到文件里面\n);
} test.c文件 //test.c文件
#includeSList.hvoid menu()
{printf(********************通讯录********************\n);printf(* 1, 增加联系人 2删除联系人 *\n);printf(* 3修改联系人 4查找联系人 *\n);printf(* 5展示联系人 6存储到文件 *\n);printf(********************0退出*********************\n);
}
int main()
{int input 1;PeoInfo* info NULL;//InitContact(info);do{menu();printf(输入数值进行通讯录的使用操作:\n);scanf(%d, input);switch (input){case 1:AddContact(info);break;case 2:DelContact(info);break;case 3:ModifyContact(info);break;case 4:FindContact(info);break;case 5:ShowContact(info);break;case 6:LoadContact(info);break;case 0://DestroyContact(info);printf(退出成功\n);break;default:printf(请选择正确的数值\n);break;}} while (input ! 0);return 0;
} SList.h文件 //SList.h文件
#define _CRT_SECURE_NO_WARNINGS 1
#includestdio.h
#includeassert.h
#includestdlib.h
#includestring.h
#includeAddress_Book.h//通讯录的前置声明
#pragma once//自定义类型
//typedef int SLTDataType;//自定义类型
typedef PeoInfo SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//链表的初始化
void SLInfo(SLTNode** pphead);//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x);打印链表
//void SLTPrint(SLTNode* pphead);//链表的销毁
void SListDesTroy(SLTNode** pphead);//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//链表的尾删
void SLTPopBack(SLTNode** pphead);//链表的头删
void SLTPopFront(SLTNode** pphead);//链表的查找
//SLTNode* SLTFind(SLTNode* pphead, SLTDataType x);//链表的指定位置的删除
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除指定位置之后的数据1
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);
//删除指定位置之后的数据2
void SLTEraseAfterPlus(SLTNode* pos);//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x); SList.c文件 //SList.c文件#includeSList.h
//链表的申请空间
SLTNode* SLBuyNode(SLTDataType x)
{SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(newnode:error);exit(1);}newnode-data x;newnode-next NULL;return newnode;
}//链表的初始化
void SLInfo(SLTNode** pphead)
{// 创建头结点SLTNode* newnode (SLTNode*)malloc(sizeof(SLTNode));if (newnode NULL){perror(malloc failed);exit(1);}*pphead newnode;// 初始化头结点的成员(*pphead)-next NULL;
}//链表的销毁
void SListDesTroy(SLTNode** pphead)
{assert(pphead *pphead);SLTNode* pure *pphead;while (pure){//存储节点不能直接进行销毁如果直接是purepure-next;那么就会导致找不到下一个节点了SLTNode* next pure-next;//销毁当前节点free(pure);//指向下一个节点这里不需要指向null因为销毁了你不使用了pure next;}//头结点指向空*pphead NULL;printf(销毁成功\n);
}//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode SLBuyNode(x);if (*pphead NULL){*pphead newnode;}else{SLTNode* pure *pphead;//这里的循环条件必须是pure-next 因为只是pure的情况下循环里面就会产生到最后一个指针的情况下对null解应用while (pure-next){pure pure-next;}pure-next newnode;}
}//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead NULL){*pphead newnode;} else{newnode-next *pphead;*pphead newnode;}
}//链表的尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead *pphead);//当只有一个节点的时候我们为了防止释放之后变成野指针因为释放的当前位置等于null之后free(nodetile);nodetile NULL;//你继续进行下一个节点的置为nullpure-next NULL;会导致越界访问//所以我们进行一个判断也就只有一个节点的时候if ((*pphead)-next NULL){free(*pphead);*pphead NULL;}else{SLTNode* nodetile *pphead;SLTNode* pure *pphead;while (nodetile-next){pure nodetile;nodetile nodetile-next;}free(nodetile);nodetile NULL;pure-next NULL;}
}//链表的头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead *pphead);//这里不需要判断是不是只有一个头结点因为删除时候下一个节点也只是NULL我们只是把NULL赋值到新的头结点没有越界访问SLTNode* newhead (*pphead)-next;free(*pphead);*pphead NULL;*pphead newhead;
}
//链表的指定位置的删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead pphead);if (*pphead pos){SLTNode* newhead (*pphead)-next;free(*pphead);*pphead newhead;}else{//需要删除的节点SLTNode* del *pphead;while (del-next ! pos){del del-next;}del-next pos-next;free(pos);pos NULL;}
}//删除指定位置之后的节点
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{assert(pphead *pphead);SLTNode* del *pphead;SLTNode* next pos-next;while (del ! pos){del del-next;}if (del-next NULL){perror(null:);}pos-next next-next;free(next);next NULL;
}//删除指定位置之后的数据
void SLTEraseAfterPlus(SLTNode* pos)
{assert(pos);SLTNode* del pos-next;if (del-next NULL){perror(null:);}pos-next del-next;free(del);del NULL;
}//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead *pphead);SLTNode* newnode SLBuyNode(x);if (*pphead pos){SLTPushFront(pphead, x);}else{SLTNode* pure *pphead;while (pure-next ! pos){pure pure-next;}newnode-next pos;pure-next newnode;}
}//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode SLBuyNode(x);newnode-next pos-next;pos-next newnode;
} 测试 没有问题