SList.c
#include"SList.h"
#include<malloc.h>
#include<assert.h>
#include<stdio.h>
//申请节点
SListNode* BuySListNode(SLDataType data)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (NULL == newNode)
{
assert(0);//调试宏,参数为0宏就会触发,非0不会触发
//我们将其为零,意思就是如果为NULL时,会触发,提醒我们一下
return NULL;//申请空间失败
}
//如果申请空间没有问题
newNode->next = NULL;
newNode->data = data;
return newNode;
}
//尾插 链表中有节点
//过程:
//1.找到链表中的最后一个节点
//2.尾插新节点
//1.(1)链表中没有节点
// head->NULL
// (2)链表中有节点
// head->1->2->3->4->NULL
// 丨->5->NULL
//想要在函数中通过形参head来修改外部实参head指针的指向,一级指针函数做不到
//因为head是外部实参head的一份拷贝
//因此这里需要传递二级指针
//链表不存在:链表不存在即头指针head都没有定义
//链表为空:head--》NULL,表示链表中没有节点
//尾插:
void SListPushBack(SListNode** head, SLDataType data)
{
assert(head);//head必定不为NULL,这里检测的是程序的非法输入
//空链表
SListNode* newNode = BuySListNode(data);//申请节点
if (NULL == *head)
{
*head = newNode;
}
else
{
//1.找到链表中的最后一个节点
// 问题?怎么才算是找到了最后一个节点?(next = NULL)
SListNode* cur = *head;//head不为NULL
while (cur->next)
{
//cur++;不能这样写,因为不是连续的空间
cur = cur->next;
}
//2.插入新节点
cur->next = newNode;
}
}
//1.参数检测
//2.程序逻辑
//尾删:每次要删除链表中的最后一个节点
// 1.找最后一个节点(即next为空的节点)
// 2.删除最后一个节点
void SListPopBack(SListNode** head)
{
assert(head);//检测非法情况
if (NULL == *head)
{
//空链表
return;
}
else if (NULL == (*head)->next)
{
//链表中只有一个节点
free(*head);
*head = NULL;
}
else
{
//链表非空--链表中至少有一个节点
SListNode* cur = *head;
SListNode* prev = NULL;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
//最后一个节点已经找到
//删除该节点
free(cur);
}
}
//1.参数检测
//2.程序逻辑
void SListPushFront(SListNode** head, SLDataType data)//头插
{
assert(head);//保证链表在里面存在
SListNode* newNode = BuySListNode(data);
newNode->next = *head;
*head = newNode;
//链表在插入时,不需要改变其他元素位置,效率特别高,时间复杂夫O(1)
//1.空链表
//if (NULL == *head)
//{
// *head = newNode;
//}
//else
//{
// //2.链表有多个节点
// newNode->next = *head;
// *head = newNode;
//}
}
//head保存的是头指针的地址
//1.先标记 2.移动head 3.删除节点
void SListPopFornt(SListNode** head)//头删
{
SListNode* delNode = NULL;
assert(head);//断言链表存在
if (NULL == *head)
return;
delNode = *head;
*head = delNode->next;
free(delNode);
}
void SListInsertAfter(SListNode* pos, SLDataType data)//任意位置插入
//问题:任意位置的插入需要给定三个参数,此处只有两个参数?
// 答:兼容性,c++中会直接给一个这样的链表,为了和它能够兼容
//问题:给一个为什么插入的是后面呢?
// 答:我们不可能从链表中一个元素推断出这个元素的前面一个元素
//过程:1.让新元素的next指向下一个元素
// 2.让新元素插入的位置(元素)的next指向新元素
{
SListNode* newNode = NULL;
if (NULL == pos)
return;
newNode = BuySListNode(data);
newNode->next = pos->next;
pos->next = newNode;
}
//pos不能是最后一个节点
void SListEraseAfter(SListNode* pos)//任意位置删除 删除和上面插入同理
{
SListNode* delNode = NULL;
if (NULL == pos || NULL == pos->next)
return;
delNode = pos->next;
pos->next = delNode->next;
free(delNode);
}
//head指向的就是链表中第一个节点
//统计链表中有多少个节点
int SListSize(SListNode* head)
{
//assert(head);不能用这个断言,因为此处的如果为空链表,空链表是合法情况
//if (NULL == head)
// return 0;
//当链表为空时,下面的while不运行,count也是0;重复可删除
SListNode* cur = head;
int count = 0;
while (cur)
{
count++;
cur = cur->next;
}
return count;
}
//判断链表是否为空
int SListEmpty(SListNode* head)
{
return NULL == head;
}
//在链表list中寻找一下data这个元素,找到返回节点,没有找到返回空
SListNode* SListFind(SListNode* head, SLDataType data)
{
SListNode* cur = head;
while (cur)
{
if (cur->data == data)
return cur;
cur = cur->next;
}
return NULL;
}
//将链表中的有效节点销毁
//采用头删法将列表中一个一个的删除
void SListDestroy(SListNode** head)
{
assert(head);
while (*head)
{
SListNode* delNode = *head;
*head = delNode->next;
free(delNode);
}
}
//打印一下列表
void PrintSList(SListNode* head)
{
SListNode* cur = head;
while (cur)
{
printf("%d--->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void TestSList1()//将单链表做一下
{
SListNode* listhead = NULL;//head是在外部,是实参
SListPushBack(&listhead, 1);
SListPushBack(&listhead, 2);
SListPushBack(&listhead, 3);
SListPushBack(&listhead, 4);
SListPushBack(&listhead, 5);
//SListPushBack(NULL, 5);非法调用,应该使用assert让程序停止下来,调试
SListPopBack(&listhead);
PrintSList(listhead);
SListDestroy(&listhead);//链表测试完,需要将链表中所有元素删除,否则会造成元素丢失。
}
void TestSList2()
{
SListNode* listhead = NULL;
SListPushFront(&listhead, 1);
SListPushFront(&listhead, 2);
SListPushFront(&listhead, 3);
SListPushFront(&listhead, 4);
SListPushFront(&listhead, 5);
PrintSList(listhead);
SListDestroy(&listhead);
}
void TestList3()
{
SListNode* listhead = NULL;
SListPushBack(&listhead, 1);
SListPushBack(&listhead, 2);
SListPushBack(&listhead, 3);
SListPushBack(&listhead, 4);
SListPushBack(&listhead, 5);
SListInsertAfter(SListFind(listhead, 3), 0);
PrintSList(listhead);
SListInsertAfter(SListFind(listhead, 100), 10);//没有这个位置,看插入情况
PrintSList(listhead);
SListEraseAfter(SListFind(listhead, 0));
PrintSList(listhead);
SListEraseAfter(SListFind(listhead, 5));
PrintSList(listhead);
}
void TestSList()
{
//TestSList1;
//TestSList2();
TestList3();
}
SList.h
//SList.h
#pragma once//防止头文件引用重复
typedef int SLDataType;
typedef struct SListNode
{
//存储一个指向下一个元素的地址 SListNode
struct SListNode* next; //指向下一个节点的地址
SLDataType data;//存储该节点中的数据
}SListNode;
//注意:
//如果想要在函数中改变头指针的指向,形参必须为二级指针
//如果不需要在函数中改变头指针的指向,只需要传递一级指针即可
//单链表中我们需要什么操作
void SListPushBack(SListNode** head, SLDataType data);//尾插
//我们往SListNode* list里面插入SLDataType data
void SListPopBack(SListNode** head);//尾删
void SListPushFront(SListNode** head, SLDataType data);//头插
void SListPopFornt(SListNode** head);//头删
void SListInsertAfter(SListNode* pos, SLDataType data);//任意位置插入
//任意位置的插入需要给定三个参数,此处只有两个参数
void SListEraseAfter(SListNode* pos);//任意位置删除
//统计链表中有多少个节点
int SListSize(SListNode* head);
//判断链表是否为空
int SListEmpty(SListNode* head);
//在链表list中寻找一下data这个元素,找到返回节点,没有找到返回空
SListNode* SListFind(SListNode* head, SLDataType data);
//将链表中的有效节点销毁
void SListDestroy(SListNode** head);
void TestSList();//将单链表做一下
test.c
下面有一部分是课外延伸,需要代码请注意:题外延申以下不要借鉴
题外延申(做代码中,难免会用到一些知识点,我将其中有些难的写下来,方便你我查找资料)
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
int main()
{
TestSList();
return 0;
}
//题外延申:
//if 0
//。。。。。
//endif
//屏蔽代码
//一个程序从写完到可以执行起来,需要经过一下几个步骤:预处理--》编译--》汇编--》链接--》生成可执行程序
//程序链接出错
//错误中:无法解析的外部符号--(问题出自于)--》链接
//c语言中的传参方式
//值传递:形参(left,right)是实参(a,b)的一份拷贝,在函数中交换的是形参(即实参的拷贝)
// 形参与实参没有任何关联--》在函数中最形参进行修改不会影响外部的实参
//值传递
//void Swap(int left, int right)
//{
// int temp = left;
// left = right;
// right = temp;
//}
//int main()
//{
// int a = 10;
// int b = 20;
//
// Swap(a,b);
// printf("%d %d", a, b);
// return 0;
//}
//传地址:pa,pb实际指向 的就是实参a,b
// 在函数中对形参指向空间中的内容进行修改,实际修改的就是实参本身
//void Swap(int* pa, int* pb)
//{
// int temp = *pa;
// *pa = *pb;
// *pb = temp;
//}
//int main()
//{
// int a = 10;
// int b = 20;
//
// Swap(&a, &b);
// printf("%d %d", a, b);
// return 0;
//}
//在Swap函数中如果想要交换外部两个指针实参的指向,必须传递二级指针
//void Swap(int** pleft, int** pright)
//{
// int* temp = *pleft;
// *pleft = *pright;
// *pright = *temp;
//
//}
//
//int main()
//{
// int a = 10;
// int b = 20;
//
// int* p1 = &a;
// int* p2 = &b;
//
// //通过Swap函数交换p1和p2两个指针的指向
// Swap(&p1, &p2);
//
// printf("%d %d", a, b);
// return 0;
//}
//传参总结:
//传值:实参是形参的一份拷贝,不能通过形参改变外部的实参
//传地址:形参中就是实参的地址,可以通过对形参解引用拿到实参。可以通过修改形参来达到对实参的改变
//因此:如果想要通过形参来改变实参,必须传递实参的地址
//如果实参是普通类型的变量比如:int a ---》&a--》一级指针
//如果实参是指针类型--》int* pa = &a;在函数中想要修改实参pa的指向,必须要传递pa的地址,即int** 二级指针
//我们需要不带头的单链表,链表里面都是由一个一个节点构成的,我们需要
//将链表组织起来,我们只需要一个指针,这个指针指向链表中的第一个节点
//如果链表里面没有放节点,接下来我们需要放节点,刚开始我们需要
//Node* head = NULL;
//然后我们需要通过
//SListPushBack(head,1);
//1.申请新节点newNoad
//2.尾插新节点
// (1)head为NULL:链表中没有节点,head指向newNode
// (2)head不为NULL:链表中已经有节点
// 找到链表中的最后一个节点--》如果某个节点的next域指向空,则该节点为最后一个节点
// 将新节点链接到最后一个节点之后
//如果想要在函数中用过形参改变外部的实参,则必须传递实参的地址
//难点:在函数中想要通过形参改变外部实参指针的指向,则必须传递外部实参指针的地址
//不论是值传参还是指针传参,在传参过程中都会生成一份拷贝
//(如果是值,那么会生成一份值的拷贝,如果是指针,则会生成一份指针的拷贝,
// 如果在函数中改变函数的指向,他最终改变的是副本的指向)
//对于链表的基本操作中,哪些方法需要传递二级指针,哪些方法需要传递一级指针?
//本质:在函数体中是否需要通过形参指针改变外部实参指针的指向
// 需要:函数的参数必须为二级指针
// 比如:所有的插入,删除,销毁
// 不需要:函数的参数只需要传递一级指针
// 比如:查找,获取有效节点的个数
//参数检测
//有些情况下使用assert:该种问题如果发生,程序是一张错误--》除法的除数为零
//有些情况下使用if判断:该种问题如果发生,程序是一种正常的情况,--》空链表--》合法的链表,只不过链表中没有节点而已
//free(cur)
//注意:不是销毁cur本身,二十销毁cur指向的那个节点
//free(void* addr):释放addr表示的空间