链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
每一个结构体类型都有两个成员,一个用于存储需要的数据,一个负责指向下一个结构体的地址,使他们之间链接起来,这里以无头不循环单链表来学习
头文件的创建
#ifndef SLIST_H
#define SLIST_H
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int code; 方便更改数据类型
typedef struct SLTNode
{
code data;
struct SLTNode* next;
}STL;
#endif SLIST_H
先防重定义,在把可能用到的头文件放到该目录下,之后创建一个结构体类型及其成员,必须要有一个结构体指针用与链接别的结构体
接口函数
下面创建需要的函数
先创建一个申请动态内存的函数
STL* STList_BuyListNodet(int data)
{
STL* newcur = (STL*)malloc((sizeof(STL)));
if(newcur == NULL)
{
perror("malloc");
exit(-1);
}
newcur->data = data;
newcur->next = NULL;
return newcur;
}
用于接下来的尾增、头增、随机增
2.尾增
void STList_PushBack(STL** sp,int data)
{
STL* newList = STList_BuyListNodet(data);
STL* cur = *sp;
if(cur == NULL)
{
*sp = newList;
}
else
{
while(cur->next != NULL)
{
cur = cur->next;
}
cur->next = newList;
}
}
3.尾删
void STList_PushBack(STL** sp)
{
assert(*sp);
STL* cur = *sp;
if(cur->next == NULL)
{
free(cur);
*sp = NULL;
}
else
{
STL* del = NULL;
while(cur->next)
{
del = cur;
cur = cur->next;
}
free(cur);
del->next = NULL;
}
}
4.打印
方便调试,创建好一个函数就调试一次
void STList_print(STL* sp)
{
STL* cur = sp;
while(cur)
{
printf("%d -> ",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
5.头增
void STList_PushFront(STL** sp,int x)
{
STL* newList = STList_BuyListNodet(data);
newList->next = *sp;
*sp = newList;
}
6.头删
void STList_PopFront(STL** sp)
{
assert(*sp);
STL* cur = *sp;
*sp = cur->next;
free(cur);
}
7.查找
STL* STList_find(STL* sp,int data)
{
STL* cur = sp;
while(cur)
{
if(cur->data == data)
return cur;
cur = cur->next;
}
return NULL;
}
8.随机增(头)
需要考虑如果是头指针指向的地址该如何处理
void SList_Front(STL** sp,STL* pos,int x)
{
STL* newList = STList_BuyListNodet(data);
STL* cur = *sp;
if(pos == *sp)
{
newList->next = *sp;
*sp = newList;
}
else
{
while(cur->next != pos)
{
cur = cur->next;
}
newList->next = pos;
cur->next = newList;
}
}
9.随机增(尾)
void SList_Back(STL** sp,STL* pos,int x)
{
STL* newList = STList_BuyListNodet(data);
newList->next = pos->next;
pos->next = newList;
}
10.指定删
需要考虑如果是删除第一个地址该如何处理
void SList_Pop(STL** sp,STL* pos)
{
assert(*sp);
STL* cur = *sp;
if(pos == *sp)
{
*sp = pos->next;
free(cur);
}
else
{
while(cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
11.全部释放
void SList_Destory(STL** sp)
{
STL* cur = *sp;
STL* del = NULL;
while(cur)
{
del = cur;
cur = cur->next;
free(del);
}
*sp = NULL;
}
一些功能的复用
1.查找+指定删
STL* data;
STL* pos = NULL;
while( pos = STList_find(data,4) )
{
SList_Pop(data,pos);
}
//STL* STList_find(STL* sp,int data)
//void SList_Pop(STL** sp,STL* pos)
2.头删复用指定删
void STList_PopFront(STL** sp)
{
assert(*sp);
if(*sp->next == NULL)
{
SList_Pop(sp,*sp);
*sp = NULL;
}
else
{
SList_Pop(sp,*sp);
}
}
3.头增复用随机增(头)
void STList_PushFront(STL** sp,int x)
{
SList_Front(STL** sp,*sp,x);
}
完成后的头文件
#ifndef SLIST_H
#define SLIST_H
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int code;
typedef struct SLTNode
{
code data;
struct SLTNode* next;
}STL;
//打印
void STList_print(STL* sp);
//尾增
void STList_PushBack(STL** sp,int x);
//尾删
void STList_PopBack(STL** sp);
//头增
void STList_PushFront(STL** sp,int x);
//头删
void STList_PopFront(STL** sp);
//查找
STL* SList_find(STL** sp,int x);
//指定插入(头)
void SList_Front(STL** sp,STL* pos,int x);
//指定删除
void SList_Pop(STL** sp,STL** pos);
//指定插入(尾)
void SList_Back(STL** sp,STL* pos,int x);
//全部释放
void SList_Destory(STL** sp);
#endif SLIST_H
小结:
1.形参是二级指针的意义:
若传递的是一级指针,假如传进来的一级指针是NULL,那么形参是实参的临时拷贝也为NULL,申请的动态内存地址被形参保存,不会改变实参,在函数结束时,临时变量会随机销毁(将在栈区临时开辟的内存返回给栈帧)而动态内存(堆区)的地址并未被释放,内存的入口也没作为返回值返回的话,就会造成内存泄漏
若形参的是二级指针时,传递的是一级指针的地址,当一级指针为NULL,可以通过解引用间接访问到一级指针本身
2.对于尾增需要考虑如果是当前为NULL该怎么处理,尾删则要考虑剩最后一个地址怎么处理
3.通过上面那些函数可以看出,不循环单链表对于尾删尾增都需要遍历到最后位置,尾删还需要一个指针保存前一个位置,比较麻烦;而头增头删很方便,也不需要考虑其他特殊情况
4.在实际编写时,最好是写完一个模块就测试一个模块
5.除非特别熟练,否则应该边画图边编写