为什么要写这篇
本意是为了将扫描到的蓝牙设备列表 按照接收灵敏度存放到链表中,我一个同事是这么做的,我当时用结构体数组排序不香吗反驳了回去,因为自己没怎么用过链表,我自己都怀疑自己有点儿酸葡萄心理,然后就自己查资料实现了个链表。主要还是自己理解,参考资料有C Primer Plus的链表(抽象数据类型ADT) 和 数据结构笔记【单链表】
说在前面的话
要测试的话,请先搭建环境:编译C代码环境搭建
要源文件的话,请点击链接:LinkedList.zip
链表的实现有很多种,但更重要的是自己有清晰的思路,知道会用到哪些操作,这篇文章真是费时不少,收获嘛,还没有将链表结点用到实处,真是那句话说得好:行百里者半九十。
为链表添加了一个记录项数的变量,带来的直接好处是判断为空为满直接将该变量与数值做比较即可,否则就是查询头结点是否为NULL(为空判断),是否已经申请不到内存(为满判断),那样并不适用于实际使用情况。这是一个很关键的变量。
关于只有指向头结点的指针,没有指向尾结点的指针,可能也是为了和之前实现的递推均值滤波算法---链式队列实现相区分开吧,会不会有什么影响?对于单向链表来说,指向尾结点的指针只能继续往后指,所以只有在链表尾部添加结点时会受影响,其它函数不受任何影响。也会尽快补上这个不完美的地方。事实证明,链表初始化函数,插入函数、删除函数也受影响。
关于清空链表,按照理解来说,该从尾结点一个个删除比较符合理解习惯,但按照效率来讲,队列的那种从头结点删除效率更高,因为链表是单向的。两种方式我都实现且测试了,但更推荐队列的那种。
对于链表来说,目前这种是个人认为最通用的,最容易理解的,但不见得是最简单的,因为接口函数很多。写注释写到都快吐了,这应该是我写的最丰富的注释,因此容易理解。这还只是各基础,之后可能添加尾结点,将结点数据域改成结构体,实现双向链表。
按照C Primer Plus中的抽象数据类型ADT相关的名词:建立抽象,建立接口,实现接口,测试接口来定义标题。
建立抽象
在链表实现中,每个链节叫作结点,每个结点包含形成链表内容的信息和指向下一个结点的指针。结点结构的标记名为Node。链表结构的标记名为LinkedList,包含指向头结点结构的指针和标记链表项数的变量。
建立接口
#ifndef __LINKED_LIST_H__
#define __LINKED_LIST_H__
#include <stdbool.h> /* 定义bool 类型 */
#include <stdio.h> /* 定义输入输出 */
#include <stdint.h> /* 定义常用数据类型 */
#include <stdlib.h> /* 定义 malloc free exit */
#define DEBUG_OUTPUT 1
#define MAXQUEUE 10
typedef int32_t Item;
typedef uint32_t L_DATA_type;
typedef int32_t L_POSITION_type;
typedef uint16_t L_NUMBER_type;
/* 链表结点结构 */
typedef struct node
{
Item item;
struct node *next;
}Node;
typedef struct list
{
Node* head; /* 指向链表头的指针 */
L_NUMBER_type items; /* 链表中的项数 */
}LinkedList;
/**
* 初始化链表
* 前提条件:plist指向一个链表
* 后置条件:链表被初始化为空
*/
void InitializeLinkedList(LinkedList* plist);
/**
* 清空链表
* 前提条件:plist指向被初始化的链表
* 后置条件:队列被清空
*/
void EmptyTheLinkedList(LinkedList* plist);
/**
* 检查链表是否已满
* 前提条件:plist指向已初始化的链表
* 后置条件:如果链表已满则返回true, 否则返回false
*/
bool LinkedListIsFull(const LinkedList* plist);
/**
* 检查链表是否为空
* 前提条件:plist指向被初始化的链表
* 后置条件:如果链表为空则返回true, 否则返回false
*/
bool LinkedListIsEmpty(const LinkedList* plist);
/**
* 确定链表中的项数
* 前提条件:plist指向被初始化的链表
* 后置条件:返回队列中的项数
*/
L_NUMBER_type LinkedListNodeCount(const LinkedList* plist);
/* 在链表尾部添加、删除结点 */
bool LinkedListAddNode(Item item, LinkedList* plist);
bool LinkedListDeleteNode(LinkedList* plist, Item* item);
/* 遍历链表来显示、 求和 */
void ShowLinkedListNode(const LinkedList* plist);
L_DATA_type GetLinkedListNodeSum(const LinkedList* plist);
/* 增(插入)、删、改、查 */
bool InsertNodeToLinkedList(LinkedList* plist, L_NUMBER_type position, Item item);
bool DeleteNodeFromLinkedList(LinkedList* plist, L_NUMBER_type position, Item* pitem);
bool ModifyNodeFromLinkedList(LinkedList* plist, L_NUMBER_type position, Item item);
L_POSITION_type SearchNodeFromLinkedList(LinkedList* plist, Item item);
#endif
实现接口
#include "LinkedList.h"
/* malloc 得到的 和 free释放的是什么? 得到的是一个指向新Node结点的指针, 释放的是指向新Node结点的指针 */
/*******************用于添加结点和删除结点************************/
static void CopyToNode(Item item, Node* pn);
static void CopyToItem(Node* pn, Item* pi);
/*********************显示结点数据域***********************/
static void ShowNodeItem(Item item);
/*********************用于链表值求和************************/
static void LinkedListValueSum(Item item);
static L_DATA_type GetLinkedListValueSum(void);
static void ClearLinkedListValueSum(void);
static void Traverse(const LinkedList* plist, void(*pfun)(Item item));
/**************************************************分界线***************************************************/
/**
* CopyToNode
* @brief 从结构体赋值到链表结点
* @param item 参数描述: 结构体指针
* @param pn 参数描述: 链表结点指针
*/
static void
CopyToNode(Item item, Node* pn)
{
pn->item = item;
}
/**
* CopyToItem
* @brief 从链表结点复制到结构体中
* @param pn 参数描述: 链表结点指针
* @param pi 参数描述: 结构体指针
*/
static void
CopyToItem(Node* pn, Item* pi)
{
*pi = pn->item;
}
static uint8_t term_val = 0; ///< 变量:用于表示是第几个结点
/**
* ShowNodeItem
* @brief 显示队列结点数据域
* @param item 结点数据域
*/
static void
ShowNodeItem(Item item)
{
printf("node %d item is %d \n", term_val, item);
}
static L_DATA_type l_val_sum = 0; ///< 变量:链表求和值保存
/**
* LinkedListValueSum
* @brief 对链表各项进行求和
* @param item 参数描述: 链表结点的数据域
*/
static void
LinkedListValueSum(Item item)
{
l_val_sum += item;
}
/**
* GetLinkedListValueSum
* @brief 得到链表中的求和值
* @retval L_DATA_type 返回值描述: 求和值
*/
static L_DATA_type
GetLinkedListValueSum(void)
{
return l_val_sum;
}
/**
* ClearLinkedListValueSum
* @brief 清除链表中的求和值
*/
static void
ClearLinkedListValueSum(void)
{
l_val_sum = 0;
}
/**
* Traverse
* @brief 遍历链表,执行某种操作
* @param plist 参数描述: 链表指针
* @param pfun 参数描述: 函数指针
* @note 若结点指针不是NULL 1.对当前结点执行回调函数,操作数据域 2.前进至下一个结点
*/
static void
Traverse(const LinkedList* plist, void(*pfun)(Item item))
{
Node *pnode = plist->head;
while(pnode != NULL)
{
(*pfun)(pnode->item); /* 对当前结点执行回调函数,操作数据域 */
term_val++; ///< 变量:用于show_node_item 函数
pnode = pnode->next; /* 前进至下一个结点 */
}
#if DEBUG_OUTPUT
if(term_val == 0) /* 进入该函数时该变量值为0,若执行循环值非0,退出该函数时值清0 */
{
printf("LinkedList is Empty !, from Function: %s \n", __FUNCTION__);
}
#endif
term_val = 0; ///< 变量:用于show_node_item 函数
}
/**************************************************分界线***************************************************/
/**
* InitializeLinkedList
* @brief 初始化链表
* @param plist 参数描述: 链表指针
* @note 1.指针指向NULL 2.链表结点数为0
*/
void
InitializeLinkedList(LinkedList* plist)
{
plist->head = NULL; /* 指针指向NULL */
plist->items = 0; /* 链表结点数为0 */
}
#if 0
/**
* EmptyTheLinkedList
* @brief 清空链表(从头结点开始删除)
* @details 本函数清空链表是从开始处往尾部删除
* @param plist 参数描述: 链表指针
* @note 1.保存下一个结点的地址 2.释放当前结点 3.前进至下一个结点 4.链表结点数减1
*/
void
EmptyTheLinkedList(LinkedList* plist)
{
Node* psave;
while(! LinkedListIsEmpty(plist)) //while (plist->head != NULL)
{
psave = plist->head->next; /* 保存下一个结点的地址 */
free(plist->head); /* 释放当前结点 */
plist->head = psave; /* 前进至下一个结点 */
plist->items--; /* 链表结点数减1 */
}
#if DEBUG_OUTPUT
printf("LinkedList is Empty , from Function: %s \n", __FUNCTION__);
#endif
}
#elif 1
/**
* EmptyTheLinkedList
* @brief 清空链表(从尾结点开始删除)
* @details 每删除一个尾结点发生一次变化
* @param plist 参数描述: 链表指针
* @note 当链表不为空:删除尾结点
* @remarks 涉及到几个函数嵌套,而且对于只有指向头结点没有指向尾结点的来说,从头结点删除效率更高
*/
void
EmptyTheLinkedList(LinkedList* plist)
{
Item temp_item;
while (! LinkedListIsEmpty(plist))
{
LinkedListDeleteNode(plist, &temp_item);
}
#if DEBUG_OUTPUT
printf("LinkedList is Empty , from Function: %s \n", __FUNCTION__);
#endif
}
#endif
/**
* LinkedListIsFull
* @brief 链表是否为满
* @param plist 参数描述: 链表首指针
* @retval true 返回值描述: 链表为满
* @retval false 返回值描述: 链表非满
* @note 1.链表实际结点数与限定链表为满时结点数的判断
*/
bool
LinkedListIsFull(const LinkedList* plist)
{
return plist->items == MAXQUEUE;
}
/**
* LinkedListIsEmpty
* @brief 链表是否为空
* @param plist 参数描述: 链表指针
* @retval true 返回值描述: 链表为空
* @retval false 返回值描述: 链表非空
* @note 1.链表实际结点数与空链表时结点数的判断
*/
bool
LinkedListIsEmpty(const LinkedList* plist)
{
return plist->items == 0;
}
/**
* AddItem
* @brief 添加到链表尾部
* @param item 参数描述:
* @param plist 参数描述: 无
* @retval true 返回值描述: 无
* @retval false 返回值描述: 无
*/
bool LinkedListAddNode(Item item, LinkedList* plist)
{
L_POSITION_type temp_index;
temp_index = LinkedListNodeCount(plist); /* 获取链表中结点数 */
if (! InsertNodeToLinkedList(plist, temp_index, item)) /* 插入到链表尾部 */
{
return false;
}else
{
return true;
}
}
/**
* DeleteItem
* @brief 从链表尾结点删除一项
* @param plist 参数描述: 链表指针
* @param item 参数描述: 被删除结点的数据域
* @retval true 返回值描述: 删除成功
* @retval false 返回值描述: 删除失败
* @note 1.获取链表中结点数 2.删除尾结点
*/
bool
LinkedListDeleteNode(LinkedList* plist, Item* pitem)
{
L_POSITION_type temp_index;
temp_index = LinkedListNodeCount(plist); /* 获取链表中结点数 */
if (! DeleteNodeFromLinkedList(plist, temp_index - 1, pitem)) /* 删除尾结点 */
{
return false;
}else
{
return true;
}
}
/**
* LinkedListNodeCount
* @brief 查询链表中项的数目
* @param plist 参数描述: 链表指针
* @retval L_NUMBER_type 返回值描述: 项的数目
*/
L_NUMBER_type
LinkedListNodeCount(const LinkedList* plist)
{
return plist->items;
}
/**
* ShowQueueNode
* @brief 显示队列数据
* @param pq 队列指针
*/
void
ShowLinkedListNode(const LinkedList* plist)
{
Traverse(plist, ShowNodeItem);
}
/**
* GetLinkedListNodeSum
* @brief 得到链表数据和
* @param plist 参数描述: 链表指针
* @retval L_DATA_type 返回值描述: 链表数据和
* @note 1.遍历链表求和 2.获取数据和用于返回值 3.清除求和变量
*/
L_DATA_type
GetLinkedListNodeSum(const LinkedList* plist)
{
L_DATA_type sum;
Traverse(plist, LinkedListValueSum); /* 遍历链表求和 */
sum = GetLinkedListValueSum(); /* 获取数据和用于返回值 */
ClearLinkedListValueSum(); /* 清除求和变量 */
return sum;
}
/**
* InsertNodeToLinkedList
* @brief
* @param plist 参数描述: 链表指针
* @param position 参数描述: 位置,取值范围: 0~plist->items
* @param item 参数描述: 结点数据域
* @retval true 返回值描述: 插入成功
* @retval false 返回值描述: 插入失败
* @note 1. 若位置不可能插入到当前链表中,返回失败
* @note 2. 若位置为0,创建一个结点,将数据域写入,该结点指向首结点,该结点成为新的首结点,链表结点数加1,返回成功
* @note 3. 若为空链表,返回失败,否则 在要插入位置前一个结点停止,创建一个结点,将数据域写入,该结点指向位置前一个结点指向的结点,该结点成为对应位置处的结点,链表结点数加1,返回成功
*/
bool InsertNodeToLinkedList(LinkedList* plist, L_NUMBER_type position, Item item)
{
Node* pnode = plist->head;
if(position > plist->items) /* 等于plist->items时表示插入到链表尾部 */
{
return false; /* 若位置不可能插入到当前链表中,返回失败 */
}else if(position == 0)
{
Node* pnew = (Node*)malloc(sizeof(Node)); /* 创建一个结点 */
CopyToNode(item, pnew); /* 将数据域写入 */
pnew->next = pnode; /* 该结点指向首结点 */
plist->head = pnew; /* 该结点成为新的首结点 */
plist->items++; /* 链表结点数加1 */
return true; /* 返回成功 */
}else
{
if(pnode == NULL)
{
return false; /* 若为空链表,返回失败 */
}else
{
for(L_NUMBER_type i = 0; i < position-1; i++) /* 在要插入位置前一个结点停止 */
{
pnode = pnode->next;
}
Node* pnew = (Node*)malloc(sizeof(Node)); /* 创建一个结点 */
CopyToNode(item, pnew); /* 将数据域写入 */
pnew->next = pnode->next; /* 该结点指向位置前一个结点指向的结点 */
pnode->next = pnew; /* 该结点成为对应位置处的结点 */
plist->items++; /* 链表结点数加1 */
return true; /* 返回成功 */
}
}
}
/**
* DeleteNodeFromLinkedList
* @brief 删除链表结点
* @param plist 参数描述: 链表指针
* @param position 参数描述: 位置,取值范围: 0~plist->items - 1
* @param pitem 参数描述: 删除结点的数据域
* @retval true 返回值描述: 删除成功
* @retval false 返回值描述: 删除失败
* @note 1.位置为0:判断是否是空链表:是则返回失败,否则将修改链表 首结点前进到下一个结点,链表结点数减1
* @note 2.位置不在当前链表内,返回失败
* @note 3.在要删除位置前一个结点停止,将要删除的位置赋给结点指针,待删除结点的数据域通过指针返回,将删除位置前一个结点的指向删除位置后一个结点,链表结点数减1
*/
bool DeleteNodeFromLinkedList(LinkedList* plist, L_NUMBER_type position, Item* pitem)
{
Node* pnode = plist->head;
if(position == 0)
{
if(pnode == NULL)
{
return false; /* 是空链表,返回失败 */
}else
{
plist->head = pnode->next; /* 首结点前进到下一个结点 */
plist->items--; /* 链表结点数减1 */
return true;
}
}else if(position >= plist->items)
{
return false; /* 位置不在当前链表内,返回失败 */
}else
{
for(L_NUMBER_type i = 0; i < position-1; i++) /* 在要删除位置前一个结点停止 */
{
pnode = pnode->next;
}
Node* pn = pnode->next; /* 将要删除的位置赋给结点指针 */
CopyToItem(pn, pitem); /* 待删除结点的数据域通过指针返回 */
pnode->next = pn->next; /* 将删除位置前一个结点的指向删除位置后一个结点 */
plist->items--; /* 链表结点数减1 */
pn = NULL;
return true;
}
}
/**
* ModifyNodeFromLinkedList
* @brief 修改链表中特定位置结点的数据域
* @param plist 参数描述: 链表指针
* @param position 参数描述: 位置,取值范围:0~plist->items - 1
* @param item 参数描述: 结点数据域
* @retval true 返回值描述: 修改成功
* @retval false 返回值描述: 修改失败
* @note 1. 位置已不在当前链表内,返回失败 2.当前为空链表,返回失败 3.找到对应位置,修改数据域,返回成功
*/
bool ModifyNodeFromLinkedList(LinkedList* plist, L_NUMBER_type position, Item item)
{
Node* pnode = plist->head;
if(position >= plist->items)
{
return false; /* 位置已不在当前链表内,返回失败 */
}
if(pnode == NULL)
{
return false; /* 当前为空链表,返回失败 */
}else
{
for(L_NUMBER_type i = 0; i <= position-1; i++) /* 找到对应位置 */
{
pnode = pnode->next;
}
pnode->item = item; /* 修改数据域 */
return true; /* 返回成功 */
}
}
/**
* SearchNodeFromLinkedList
* @brief 对链表结点逐个判断,查找item所在位置
* @param plist 参数描述: 链表指针
* @param item 参数描述: 结点数据域
* @retval int16_t 返回值描述: -1,表示没有找到,其它,表示在链表中的位置(从0开始)
* @note 1.当指向链表结点的指针不是NULL:链表数据域进行判断:成功则返回位置,失败则前进至下一个结点,结点位置加1 2.链表已遍历完
*/
L_POSITION_type SearchNodeFromLinkedList(LinkedList* plist, Item item)
{
Node* pnode = plist->head;
L_POSITION_type temp_position = 0;
while(pnode != NULL) /* 当指向链表结点的指针不是NULL */
{
if(pnode->item == item) /* 链表数据域进行判断 */
{
return temp_position;
}else
{
pnode = pnode->next; /* 前进至下一个结点 */
temp_position++; /* 结点位置加1 */
}
}
return -1; // -1,表示没有找到 /* 链表已遍历完 */
}
测试接口---测试结果
插入到头结点和尾节点
删除尾结点和头结点
修改尾结点和头结点
查找尾结点和头结点的数据,对链表结点求和取平均
清空链表,打印链表
因为测试清空链表的从尾结点开始清除,重新测试了一下
测试接口---代码实现
虽然测试接口交互性做得还不怎么样,但绝对把所有函数,所有特殊情况都试了
测试代码逻辑
测试代码
#include <stdio.h> /* 调用printf 函数 */
#include <time.h> /* 调用time 函数 */
#include <stdlib.h> /* 调用srand rand 函数 */
#include "LinkedList.h" /* 调用LinkedList的数据类型和操作函数 */
#define DEBUG_OUTPUT 1
int main(void)
{
LinkedList line;
Item temp_item;
srand((unsigned)time(NULL));
InitializeLinkedList(&line);
for(uint8_t i=0; i<5; i++)
{
if(! LinkedListIsFull(&line))
{
temp_item = rand() % 256;
#if DEBUG_OUTPUT
printf("random_val is: %d \n", temp_item);
#endif
LinkedListAddNode(temp_item, &line);
}else
{
ShowLinkedListNode(&line);
L_DATA_type sum = GetLinkedListNodeSum(&line);
printf("\n LinkedList sum is %d, average value is: %d \n\n",sum, sum/MAXQUEUE);
break;
}
}
ShowLinkedListNode(&line);
printf("*********START insert linkedList to head node*********** \n");
temp_item = rand()%256;
printf("random_val is: %d \n", temp_item);
if (! InsertNodeToLinkedList(&line, 0, temp_item))
{
printf("insert to linkedList error ! \n");
}
ShowLinkedListNode(&line);
uint16_t temp_index = LinkedListNodeCount(&line);
printf("*********FINISH inserted linkedList num is: %d*********** \n\n", temp_index);
printf("*********START insert linkedList to tail node*********** \n");
temp_item = rand()%256;
printf("random_val is: %d \n", temp_item);
if (! InsertNodeToLinkedList(&line, temp_index, temp_item))
{
printf("insert to linkedList error ! \n");
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH inserted linkedList num is: %d*********** \n\n", temp_index);
printf("*********START delete tail node from linkedList*********** \n");
if (! DeleteNodeFromLinkedList(&line, temp_index - 1, &temp_item))
{
printf("delete item from linkedList error! \n");
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH deleted linkedList num is: %d*********** \n\n", temp_index);
printf("*********START delete head node from linkedList*********** \n");
if (! DeleteNodeFromLinkedList(&line, 0, &temp_item))
{
printf("delete item from linkedList error! \n");
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH deleted linkedList num is: %d*********** \n\n", temp_index);
printf("*********START modify tail node from linkedList*********** \n");
temp_item = rand()%256;
Item temp_tail_item = temp_item;
printf("random_val is: %d \n", temp_item);
if (! ModifyNodeFromLinkedList(&line, temp_index-1, temp_item))
{
printf("modify item from linkedList error! \n");
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH modified linkedList num is: %d*********** \n\n", temp_index);
printf("*********START modify head node from linkedList*********** \n");
temp_item = rand()%256;
printf("random_val is: %d \n", temp_item);
if (! ModifyNodeFromLinkedList(&line, 0, temp_item))
{
printf("modify item from linkedList error! \n");
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH modified linkedList num is: %d*********** \n\n", temp_index);
printf("*********START search from linkedList*********** \n");
printf("temp_tail_item is: %d \n", temp_tail_item);
int16_t temp_search_index = SearchNodeFromLinkedList(&line, temp_tail_item);
if(temp_search_index == -1)
{
printf("search item from linkedList error! \n");
}else
{
printf("the position of linkeList is: %d \n", temp_search_index);
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH searched linkedList num is: %d*********** \n\n", temp_index);
printf("*********START search from linkedList*********** \n");
printf("temp_item is: %d \n", temp_item);
temp_search_index = SearchNodeFromLinkedList(&line, temp_item);
if(temp_search_index == -1)
{
printf("search item from linkedList error! \n");
}else
{
printf("the position of linkeList is: %d \n", temp_search_index);
}
ShowLinkedListNode(&line);
temp_index = LinkedListNodeCount(&line);
printf("*********FINISH searched linkedList num is: %d*********** \n\n", temp_index);
L_DATA_type sum = GetLinkedListNodeSum(&line);
printf("\n LinkedList sum is %d, average value is: %d \n\n",sum, sum/MAXQUEUE);
EmptyTheLinkedList(&line); /* 若链表清空后会有打印 */
ShowLinkedListNode(&line); /* 若链表为空会有打印 */
return 0;
}