这里我们可以看到数据存储在结构体内,结构体一部分用来存储数据,一部分用指针来存储需要链接的地址。通过这些地址,我们能灵活地查找到其数据位置,从而完成增删查改等功能。
这里我们简单为主,先构建一个结构体,一部分储存整形数据,一部分用来存地址——
#include
#include
#include
typedef int SLTDataTyp;
typedef struct SListNode
{
SLTDataTyp data;
struct SListNode* next;
}SLTNode;
这里我们用到重命名法将数据类型进行重命名,进而使其更为灵活 。
void SListTest1()
{
SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n1);
SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n2);
SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n3);
SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n4);
SLTNode* n5 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n5);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n5->data = 5;
这里可以随便用malloc函数开辟几个空间进行测试,存储数据后,将其链接起来。
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
此时我们可以通过调试的方法进行查看——
为了方便查看,我们可以先进行打印,后期也需要用到此函数——
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
增加数据可以分为头增和尾增,也可随机增加,数据增加必定要创建新的结构体,此时我们可用malloc新创建一个结构体,在三个函数(头增和尾增还有随机增加都需要用到这个函数)所以我们可以分装出一个函数。(注意,在更改链表的时候需要进行结构体传参,改变整个结构体需要传入指针变量,因为读取结构体数据本就是指针,所以这里找到数据并进行更改,形参需要传入指针变量,即这里传入二级指针)这部分可以举个简单的例子——
SLTNode* BuySLTNode(SLTDataTyp x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
return newnode;
}
这里开辟一个结构体类的函数用来开辟新的结构体,进而进行数据的增加。
然后将其与单链表链接起来——
void SLTPushFront(SLTNode** pphead, SLTDataTyp x)
{
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPushBack(SLTNode** pphead, SLTDataTyp x)
{
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
}
}
头插的话可以直接调用此函数进行增加位置,尾插的话需要分情况而定,将其分为是否为空链表和是否存在数据。
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
assert(pphead);
// 头插
if (pos == *pphead)
{
SListPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
删除数据也分为头删和尾删,删除之前需要检查链表是否为空,进而进行下一步的更改——
我们先来看下大多数人容易错的——野指针问题
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead);
while ((*pphead)->next != NULL)
{
*pphead = (*pphead)->next;
}
free(*pphead);
//释放时将*pphead前面的next指针变为了野指针,进而程序崩溃
(*pphead)->next = NULL;
}
这个思路显然很简单,但造成了野指针的问题,可进行调试查看。
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead);
//一个节点
if ((*pphead) == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* cur = *pphead;
SLTNode* tail = *pphead;
while (cur->next != NULL)
{
tail = cur;
cur = cur->next;
}
free(cur);
tail->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
*pphead = (*pphead)->next;
free(*pphead);
//*pphead = (*pphead)->next;//野指针问题
}
//也可以创建结构体指针指向节点
//void SLTPopFront(SLTNode** pphead)
//{
// assert(*pphead);
// SLTNode* next = (*pphead)->next;
// free(*pphead);
// *pphead = next;
//}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
这个较为简单,遍历即可,找到级返回结构体指针,找不到返回空——
SLTNode* SLTFind(SLTNode* phead, SLTDataTyp x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}