链表是结合结构体和指针的综合应用,学习之前务必复习结构体和指针的相关知识。
#include<stdio.h>
typedef struct TempHumiListNode_t
{
uint8_t id;
uint8_t humi;
float temp;
struct TempHumiListNode_t *next;//在ARM32平台中指针类型编译器自动分配4个字节,所以可以在结构体中嵌套一个指向本结构体类型的指针
}TempHumiListNode;
//创建头节点子函数
TempHumiListNode *InitSensorList(void)
{
TempHumiListNode *header = (TempHumiListNode)malloc(sizeof(TempHumiListNode));
if(header == NULL)
{
return NULL;//申请失败返回NULL
}
header->id = 0;
header->next = NULL;
return header;//申请成功,返回动态内存首地址
}
//添加节点函数
void AddSensorNode(TempHumiListNode *header, TempHumiListNode *node)
{
TempHumiListNode *current = header;//初始化为头节点的首地址
//寻找尾节点,没找到就循环,找到就停止
while (current->next != NULL)
{
current = current->next;
}
current->next = node;//将尾节点的next指向添加的新节点首地址
node->next = NULL;//将新节点next指向原来尾节点保存的地址
}
static TempHumiListNode *g_header;//头指针变量
//添加节点,模拟检测温湿度计函数
TempHumiListNode *FindTempHumiSensor(void)
{
TempHumiListNode *node = (TempHumiListNode *)malloc(sizeof(TempHumiListNode));
if (node == NULL)
{
return NULL;
}
static uint8_t id = 100;
node->id = id;
id--;//每次调用的时候变一下ID,模拟新设备
node->temp = 20.5f;
node->humi = 40;
return node;
}
//遍历节点
void PrintSensorData(TempHumiListNode *header)
{
TempHumiListNode *current = header->next;
//如果链表只有1个头节点,指向下一个节点时就是NULL
if (current == NULL)
{
printf("List has no node!\n");//打印链表中没人节点
return;//直接返回
}
//有节点就执行循环,直到指向NULL退出
while (current != NULL)
{
printf("\nSensor id:%d, temp = %.1f, humi = %d.\n",
current->id, current->temp, current->humi);
current = current->next;
}
}
//删除节点
//传入两个参数 头节点,数据域保存的id
void DelSensorNode(TempHumiListNode *header, uint8_t id)
{
TempHumiListNode *prev = header;//初始化指向头节点
TempHumiListNode *current = header->next;//初始化指向头节点下一个节点
while (current != NULL)
{
if (current->id == id)
{
break;//找到对应id的节点直接退出指向后面的语句
}
prev = current;
current = current->next;
}
//判断是找到id退出的,还是没找到id退出的,没找到就返回
if (current == NULL)
{
printf("Can not find sensor %d.\n", id);
return;
}
//找到了
prev->next = current->next;//将删除节点前节点指针域指向删除节点后的地址,把节点从链表中删除
free(current);//删除节点的实际内存空间,释放被删除节点的动态内存
current = NULL;//将被删除节点的指针指向NULL
}
int main(void)
{
g_header = InitSensorList();//获取头结点地址
if (g_header == NULL)
{
return -1;
}
TempHumiListNode *node;//局部变量node
for(unsigned i = 0; i < 3; i--)
{
node = FindTempHumiSensor();//调用保存数据的节点
if (node == NULL)
{
continue;//申请动态内存失败提前结束本次循环,开始下一次循环
}
AddSensorNode(g_header,node);//申请成功,将数据节点添加到链表中
}
PrintSensorData(g_header);//遍历链表的每个节点并打印数据
DelSensorNode(g_header,100);//删除掉id为100的节点
PrintSensorData(g_header);//遍历链表的每个节点并打印数据
//在添加并打印
for (uint8_t i = 0; i < 3; i++)
{
node = FindTempHumiSensor();
if (node == NULL)
{
continue;
}
AddSensorNode(g_header, node);
}
PrintSensorData(g_header);
return 0;
}
链表的创建
typedef struct TempHumiListNode_t
{
//数据域
uint8_t id;
uint8_t humi;
float temp;
//指针域
struct TempHumiListNode_t *next;//在ARM32平台中指针类型编译器自动分配4个字节,所以可以在结构体中嵌套一个指向本结构体类型的指针
}TempHumiListNode;
//创建头节点子函数
TempHumiListNode *InitSensorList(void)
{
TempHumiListNode *header = (TempHumiListNode)malloc(sizeof(TempHumiListNode));
if(header == NULL)
{
return NULL;//申请失败返回NULL
}
return header;//申请成功,返回动态内存首地址
header->id = 0;
header->next = NULL;
}
注意:如果结构体里嵌套的是结构体类型的普通变量,由于这个结构体还没有执行完毕,
编译器不知道给分配多少内存,所以会报错。
链表添加节点
尾节点的next成员保存地址为NULL;
先找到尾节点,定义一个局部变量TempHumiListNode *current,从头节点开始循环遍历节点:
TempHumiListNode *current = header;
//目标是找到边界,next保存的是NULL的那个节点,如果找到了就为假,停止循环
while(current->next != NULL)
{
current = current->next;//使current从一个节点指向下一个节点
}
找到尾节点后添加新节点到链表尾部
//添加节点函数方案1
void AddSensorNode(TempHumiListNode *header, TempHumiListNode *node)
{
TempHumiListNode *current = header;//初始化为头节点的首地址
//寻找尾节点,没找到就循环,找到就停止
while (current->next != NULL)
{
current = current->next;//从当前节点指向下一个节点
}
current->next = node;//将尾节点的next指向添加的新节点首地址
node->next = NULL;//将新节点next指向原来尾节点保存的地址
}
注意:这种方法是判断current保存的下一个地址是不是NULL,current本身指向的是尾节点,在current后面就是新的节点。
添加节点的第二种方案
使用两个指针变量遍历链表。
void AddSensorNode(TempHumiListNode *header, TempHumiListNode *node)
{
TempHumiListNode *prev = header;//初始值header
TempHumiListNode *current = header->next;//初始值header下一个节点
//寻找尾节点,没找到就循环,找到就停止,prev 指向尾节点,current指向NULL
while(current != NULL)
{
prev = current;//prev 指向下一个节点
current = current->next;//从当前节点指向下一个节点
}
//找到后将新节点添加到链表尾部
prev->next = node;//prev的next成员保存新节点的首地址
node->next = NULL;//将新节点的next成员赋值为NULL
}
注意:这种方法是判断prev指向的是不是尾节点,current保存的地址是不是尾节点的下一项NULL,current本身指向的就是尾节点的next,当current指向NULL时循环已经退出了,此时prev保存的就是为尾节点,prev的next指向的就是新的节点。
总结:方案1更简洁一些,方案2其实是为了和后面章节删除节点统一风格,方案2比较常用建以掌握。
遍历链表节点
- 考虑指针怎么指向下一个地址;
- 考虑循环的边界值,什么时候退出循环。
//遍历节点
void PrintSensorData(TempHumiListNode *header)
{
TempHumiListNode *current = header->next;
//如果链表只有1个头节点,指向下一个节点时就是NULL
if (current == NULL)
{
printf("List has no node!\n");//打印链表中没人节点
return;//直接返回
}
//有节点就执行循环,直到指向NULL退出
while (current != NULL)
{
printf("\nSensor id:%d, temp = %.1f, humi = %d.\n",
current->id, current->temp, current->humi);
current = current->next;
}
}
删除节点
- 删除的第一步还是先找到这个节点,也就是遍历;
- 找到以后退出循环,推出循环的边界值;
- 删除节点后把删除节点前一项的指针域指向删除节点的后一项。
之前在添加节点方案2时说过,遍历方式与删除节点一样,所以这里应该很容易理解
//删除节点
//传入两个参数 头节点,数据域保存的id
void DelSensorNode(TempHumiListNode *header, uint8_t id)
{
TempHumiListNode *prev = header;//初始化指向头节点
TempHumiListNode *current = header->next;//初始化指向头节点下一个节点
while (current != NULL)
{
if (current->id == id)
{
break;//找到对应id的节点直接退出
}
prev = current;
current = current->next;
}
//没找到就返回
if (current == NULL)
{
printf("Can not find sensor %d.\n", id);
return;
}
//找到了
prev->next = current->next;//将删除节点前节点指针域指向删除节点后的地址,就完成了删除节点
free(current);//释放被删除节点的动态内存
current = NULL;//将被删除节点的指针指向NULL
}
完整的代码已经放在前面了,请大家详细阅读注释,注释已经写的很清楚了,如果还是看不懂的话建议观看前面的章节,结构体与指针增强一下记忆,c语言基础部分后面会陆续更新。