1.SList.h文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SListType;
//定义一个单链表的模板
typedef struct SListNode {
SListType data;//数据域
struct SListNode* next;//指针域,存储下一个节点的地址
}SLNode;
//打印单链表的数据域的内容
void SListPrint(SLNode* phead);
//创建一个新节点
SLNode* BuySLNode(int x);
//单链表的尾插(在单链表的末尾加一个节点)
void SListPushBack(SLNode** pphead, SListType x);//因为plist是头节点的地址,是传一级指针还是传二级指针,就看有没有需要改变plist的情况(需不需要换头)
//单链表的头插(在单链表的开头加一个节点)
void SListPushFront(SLNode** pphead, SListType x);
//单链表的尾删(删除最后一个节点)
void SListPopBack(SLNode** pphead);
//单链表的头删(删除第一个节点)
void SListPopFront(SLNode** pphead);
//单链表中查找某个元素x,找到就返回其所在节点地址(才有修改的副属性),找不到就返回NULL,查找到节点地址后,可以通过地址访问到data,并修改其值
SLNode* SListFind(SLNode* phead, SListType x);//查找不需要改变plist,所以不需要传plist的地址,传plist的值就行
//在pos地址指向节点的前面插入一个新节点
void SListInsert(SLNode** pphead, SLNode* pos, SListType x);
//在pos地址指向节点的后面插入一个新节点
void SListInsertAfter(SLNode* pos, SListType x);//因为永远不可能换头节点,所以不用传&plist
//删除pos地址处的节点
void SListErase(SLNode** pphead, SLNode* pos);//有可能换头,所以必须传头节点的地址&plist
//删除pos地址处节点的后一个节点
void SListEraseAfter(SLNode* pos);//肯定不可能换头,所以不需要传&plist,也就是pphead
//销毁单链表,释放每个节点的空间
void SListDestroy(SLNode** pphead);//需要改变plist,所以要传&plist
2.SList.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListPrint(SLNode* phead)//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
{//空链表也可以打印,所以这里不需要断言
SLNode* cur = phead;//创建单链表节点实时指针cur,最开始指向首个节点
while (cur != NULL)//直到实时指针为空指针,说明没有下一个节点了,则停下
{
printf("%d->", cur->data);//通过实时指针访问节点成员data,打印
cur = cur->next;//将下个节点的地址赋值给实时指针cur,也就是实时指针cur指向下个节点
}
printf("NULL\n");//为了是打印出来的结尾很形象地表示链表的结尾
}
SLNode* BuySLNode(int x)//创建新节点
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
printf("newnode fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushBack(SLNode** pphead, SListType x)//一开始单链表还没有节点,也就是节点的指针是NULL,想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL);
//上来直接创建一个新节点(因为不管此时单链表原本有没有节点,都需要新增新节点)
SLNode* newnode = BuySLNode(x);
//若一开始单链表还没有节点,也就是*pphead是NULL,那就直接将上面开辟的新节点的地址给*pphead
if (*pphead == NULL)//其实*pphead就是plist
{
*pphead = newnode;
}
//若一开始就存在有节点,则*pphead肯定不为NULL,所以就要找尾节点,将上面开辟好的新节点的地址赋值给尾节点的成员next
else
{
SLNode* tail = *pphead;//从头开始找尾节点
while (tail->next != NULL)//找尾节点,尾节点的特点是成员next是NULL
{
tail = tail->next;
}
tail->next = newnode;//找到尾节点后,将上面开辟好的新节点的地址赋值给尾节点的成员next
}
}
void SListPushFront(SLNode** pphead, SListType x)
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL);
//上来直接创建一个新节点(因为不管此时单链表原本有没有节点,都需要新增新节点)
SLNode* newnode = BuySLNode(x);
newnode->next = *pphead;//*pphead就是plist,也就是将plist赋值给新节点的成员next
*pphead = newnode;//然后把新节点的地址赋值给plist
}
void SListPopBack(SLNode** pphead)
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL);
//删除节点前,必须要确保单链表内有节点
//温柔的方式
if (*pphead == NULL)
{
return;
}
//暴力的方式
//assert(*pphead != NULL);
//已经确保单链表内有节点,还得分只有1个节点和有2个及2个以上节点的情况
if ((*pphead)->next == NULL)//只有一个节点的情况,因为不存在倒数第二个节点,不需要把其成员next置为NULL的操作
{
free(*pphead);
*pphead = NULL;
}
else//有2个及2个以上节点的情况,因为存在倒数第二个节点,需要把其成员next置为NULL的操作
{
////方式1:找尾节点tail,前面还得有个prev记录倒数第二个节点
//SLNode* tail = *pphead;//从头开始找尾节点
//SLNode* prev = NULL;
//while (tail->next != NULL)
//{
// prev = tail;
// tail = tail->next;
//}
//free(tail);//释放最后一个节点的空间
//tail = NULL;//把最后一个节点的指针置为NULL,防止也野指针
//prev->next = NULL;//把倒数第二个节点的成员next置为NULL,防止野指针
//方式2:直接找到倒数第二个节点prev
SLNode* prev = *pphead;
while (prev->next->next != NULL)
{
prev = prev->next;
}
free(prev->next);//prev->next就是最后一个节点的地址,这句就是释放最后一个节点的空间
prev->next = NULL;//这里prev->next即表示最后一个节点的地址又表示倒数第二个节点的成员next,一句就可以把2者都置为NULL
}
}
void SListPopFront(SLNode** pphead)
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL);
//删除节点前,必须要确保单链表内有节点
//温柔的方式
if (*pphead == NULL)
{
return;
}
//暴力的方式
//assert(*pphead != NULL);
//需要把第2个节点的地址先保存,然后把第一个节点释放,最后把第2个节点的地址赋值给*pphead
SLNode* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
SLNode* SListFind(SLNode* phead, SListType x)//查找不需要改变plist,所以不需要传plist的地址,传plist的值就行
{
SLNode* cur = phead;//从头开始找
while (phead != NULL && cur->next != NULL)//单链表内无节点或者从头开始查找到最后一个节点还未找到就停下
{
if (cur->data == x)
{
return cur;//找到了就返回其节点地址
}
cur = cur->next;//到下个节点去找
}
return NULL;//找到最后一个节点都找不到就返回NULL
}
void SListInsert(SLNode** pphead, SLNode* pos, SListType x)
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL);
SLNode* newnode = BuySLNode(x);//进来直接创建一个新节点
//考虑到当pos不是首节点地址时,需要找到pos前一个节点posprev,将prev->next置为pos,
//当pos就是首节点地址时,因为不存在前一个节点,如果强行去访问,属于越界,所以必须分情况讨论
if (pos == *pphead)//当pos就是首节点地址,相当于就是头插
{
newnode->next = pos;
*pphead = newnode;
}
else//当pos不是首节点地址时,就要找到posd的前一个节点posprev
{
SLNode* posprev = *pphead;//从头开始找posprev,时间复杂度是O(n)
while (posprev->next != pos)
{
posprev = posprev->next;
}
newnode->next = pos;//找到posprev后
posprev->next = newnode;
}
}
void SListInsertAfter(SLNode* pos, SListType x)//因为永远不可能换头节点,所以不用传&plist
{
SLNode* newnode = BuySLNode(x);//进来直接创建一个新节点
if (pos == NULL)//防止找不到x的情况
{
return;
}
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SLNode** pphead, SLNode* pos)//有可能换头,所以必须传头节点的地址&plist
{ //plist可能为空,但&plist不可能为空,也就是pphead不可能为空,所以需要断言
assert(pphead != NULL && pos != NULL);
if (pos == *pphead)//如果要删除的节点是头节点,那就是必须换头
{
*pphead = pos->next;
free(pos);
}
else//如果要删除的节点不是头节点,那就要去找前一个节点
{
SLNode* posprev = *pphead;//从头开始往后找
while (posprev->next != pos)
{
posprev = posprev->next;
}
posprev->next = pos->next;
free(pos);
pos = NULL;//可以不写这句,因为pos是形参是实参的临时拷贝,pos置空不会影响实参,
//且pos是局部变量,虽然不置空就是野指针,但出了此函数就销毁了,其他地方无法访问到此野指针
}
}
void SListEraseAfter(SLNode* pos)//肯定不可能换头,所以不需要传&plist,也就是pphead
{
assert(pos != NULL);
SLNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;//这句可以不写,虽然不写的话next就是也指针,但因为next是局部变量,出了此函数,就销毁了,外面访问不到next
}
void SListDestroy(SLNode** pphead)//需要改变plist,所以要传&plist
{
assert(pphead != NULL);
SLNode* cur = *pphead;//从头开始释放
SLNode* next = NULL;
while (cur)
{
next = cur->next ;//释放前,先将其下个节点的地址保存,要不然释放后会找不到下个节点,就无法释放下个节点了
free(cur);
cur = next;
}
*pphead = NULL;//全部节点释放完后把plist置空
}
3.test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
SListPushBack(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
}
void SListTest2()
{
SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
SListPushFront(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
SListPopBack(&plist);
SListPopBack(&plist);
SListPopBack(&plist);
//SListPopBack(&plist);
SListPrint(plist);
}
void SListTest3()
{
SLNode* plist = NULL;//一开始单链表还没有节点,也就是节点的指针是NULL
SListPushFront(&plist, 1);//想要插入新节点,也就必须要改变plist的值,所以需要传plist的地址
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPrint(plist);//打印不需要改变plist的值,所以传plist就可以,不需要传plist的地址
SListPopFront(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPrint(plist);
SListPopFront(&plist);
SListPrint(plist);
}
void SListTest4()
{
SLNode* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 2);
SListPushFront(&plist, 5);
SListPrint(plist);
SLNode* pos = SListFind(plist, 2);//查找单链表内的2
int i = 1;
while (pos != NULL)//若存在多个2的情况,则一一打印出其所在节点地址
{
printf("第%d个%d的地址:%p\n", i++, pos->data, pos);
pos->data = 9;//查找还带有修改的副属性:这里找到2了,将其全部修改为9,如果只想修改第一个2,那就别用循环,直接用if
pos = SListFind(pos->next, 2);
}
SListPrint(plist);
}
void SListTest5()
{
SLNode* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 2);
SListPushFront(&plist, 5);
SListPrint(plist);
SLNode* pos = SListFind(plist, 2);//查找单链表内的第一个2
if (pos != NULL)//找到后
{
SListInsert(&plist, pos, 0);//在第一个2的前一个节点插入0
}
//如果想在第2个2前面插一个0,则应该用循环
int i = 1;
while (pos != NULL)
{
if (i == 2)
{
SListInsert(&plist, pos, 0);
}
pos = SListFind(pos->next, 2);
i++;
}
SListPrint(plist);
}
void SListTest6()
{
SLNode* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 2);
SListPushFront(&plist, 5);
SListPrint(plist);
SLNode* pos = SListFind(plist, 2);//查找单链表内的第一个2
if (pos != NULL)//找到了
{
SListInsertAfter(pos, 0);//在第一个2后插入0
}
SListPrint(plist);
////如果想在第2个2后面插一个0,则应该用循环
//int i = 1;
//while (pos != NULL)
//{
// if (i == 2)
// {
// SListInsertAfter(pos, 0);
// }
// pos = SListFind(pos->next, 2);
// i++;
//}
//SListPrint(plist);
//SListErase(&plist, pos);//删除第一个2
//SListPrint(plist);
SListEraseAfter(pos);//把第一个2后面的节点删掉
SListPrint(plist);
SListDestroy(&plist);
}
int main()
{
//SListTest1();
//SListTest2();
//SListTest3();
//SListTest4();
//SListTest5();
SListTest6();
return 0;
}