用 C 语言实现单向链表
在这篇文章中,我们将通过 C 语言实现一个简单的单向链表,并实现几个常用的链表操作,包括链表的创建、节点插入、删除、查找、更新以及打印等功能。链表是一个非常重要的线性数据结构,它与数组相比具有动态存储优势,因此在实际开发中广泛应用。

版本:
链表基础知识
链表是一种数据结构,它由一系列节点(Node)构成,每个节点包含两个部分:
- 数据域(Data):存储实际的数据。
- 指针域(Next):指向下一个节点的指针。
单向链表
单向链表是最简单的链表形式,每个节点只有一个指针域,指向下一个节点。最后一个节点的指针域为空(NULL),表示链表的结尾。

实现单向链表
我们将实现以下几个功能:
- 链表创建:创建一个空的链表。
- 节点插入:支持头部插入、尾部插入和指定位置插入。
- 节点删除:删除链表中的指定节点。
- 查找节点:查找链表中是否包含指定数据的节点。
- 更新节点:修改链表中指定节点的数据。
- 遍历链表:遍历并打印链表中的所有节点。
/**
* @file name : LinkedList.c
* @brief : 实现链表的增删改查
* @author : qrshxc@163.com
* @date : 2025/04/09
* @version : 1.0
* @note :
* CopyRight (c) 2025-2026 qrshxc@163.com All Right Reseverd
*/
1. 链表创建
链表的创建首先需要创建一个头节点。头节点不存储实际数据,但它是链表的起点,所有的操作都需要从头节点开始。我们使用 calloc 动态分配内存来创建头节点。
/**
* @name LinkedList_Create
* @brief 创建一个空链表,并对空链表进行初始化
* @param
* @return
* @retval head 头结点地址
* @date 2025/04/09
* @version 1.0
* @note
*/
LinkedList_t *LinkedList_Create(void)
{
//给头结点申请一片内存
LinkedList_t * head = (LinkedList_t *)calloc(1,sizeof(LinkedList_t));
//错误处理
if (head == NULL)
{
perror("Colloc memory for head is failed!");
exit(-1);
}
//对头结点进行初始化,注意:头结点没有数据域
head->next = NULL;
//返回头结点地址
return head;
}
2. 创建新节点
为了插入新节点,我们需要一个函数来创建一个新的节点并初始化数据和指针域。
/**
* @name LinkedList_NewNode
* @brief 创建新节点,并进行初始化(数据域+指针域)
* @param data 数据域
* @return
* @retval NewNode 新结点地址
* @date 2025/04/09
* @version 1.0
* @note
*/
LinkedList_t *LinkedList_NewNode(DataType_t data)
{
//给新结点申请一片内存
LinkedList_t *NewNode = (LinkedList_t *)calloc(1,sizeof(LinkedList_t));
//错误处理
if (NewNode == NULL)
{
perror("Colloc memory for NewNode is failed!");
return NULL;
}
//对新结点进行初始化
NewNode->data = data;
NewNode->next = NULL;
//返回新结点地址
return NewNode;
}
3.插入节点
如图:

头部插入
头部插入是将新节点插入链表的最前面。我们需要将新节点的指针域指向当前的第一个节点,然后将头节点的指针指向新节点。
/**
* @name LinkedList_HeadInsert
* @brief 头部插入数据
* @param head 头节点地址
* @param data 数据域
* @return
* @retval true 插入成功
* @retval true 插入失败
* @date 2025/04/09
* @version 1.0
* @note
*/
bool LinkedList_HeadInsert(LinkedList_t * head,DataType_t data)
{
//创建新节点,并对新节点进行初始化
LinkedList_t *NewNode = LinkedList_NewNode(data);
if (NewNode == NULL)
{
printf("Can not insert NewNode!\n");
return false;
}
//判断链表是否为空,如果为空,则直接插入
if (head->next == NULL)
{
//如果为空,则直接把新节点地址赋值给首节点
head->next = NewNode;
return true;
}
//如果不为空,则直接把新节点放在链表头部
NewNode->next = head->next;
head->next = NewNode;
return true;
}
尾部插入
尾部插入是将新节点插入链表的末尾。我们需要遍历链表找到最后一个节点,将其指向新节点。
/**
* @name LinkedList_TailInsert
* @brief 尾部插入数据
* @param head 头节点地址
* @param data 数据域
* @return
* @retval true 插入成功
* @retval true 插入失败
* @date 2025/04/09
* @version 1.0
* @note
*/
bool LinkedList_TailInsert(LinkedList_t *head, DataType_t data)
{
// 创建新节点,并对新节点进行初始化
LinkedList_t *NewNode = LinkedList_NewNode(data);
if (NewNode == NULL)
{
printf("Can not insert NewNode!\n");
return false; // 插入失败返回false
}
// 判断链表是否为空,如果为空,则直接插入
if (head->next == NULL)
{
// 如果为空,则直接把新节点地址赋值给首节点
head->next = NewNode;
return true;
}
// 如果不为空,则找到链表尾部插入新节点
LinkedList_t *phead = head->next; // 从头节点的下一个节点开始
while (phead->next != NULL)
{
phead = phead->next;
}
// 找到尾节点后插入新节点
phead->next = NewNode;
return true;
}
指定位置插入
我们还可以根据指定的目标值(destval)来插入新节点。我们遍历链表,找到目标节点后,将新节点插入到目标节点之后。
/**
* @name LinkedList_InsertByValue
* @brief 指定位置插入数据
* @param head 头节点地址
* @param data 数据域
* @param destval 目标值
* @return
* @retval true 插入成功
* @retval true 插入失败
* @date 2025/04/09
* @version 1.0
* @note
*/
bool LinkedList_InsertByValue(LinkedList_t *head,DataType_t data, DataType_t destval)
{
// 1.创建新节点,并对新节点进行初始化
LinkedList_t *NewNode = LinkedList_NewNode(data);
if (NewNode == NULL)
{
printf("Can not insert NewNode!\n");
return false; // 插入失败返回false
}
// 2.判断链表是否为空,如果为空,则直接插入
if (head->next == NULL)
{
// 如果为空,则直接把新节点地址赋值给首节点
head->next = NewNode;
return true;
}
//3.遍历链表,目的是找的目标节点,比较节点数据域
LinkedList_t *phead = head->next;//用首节点当成头结点
while(phead != NULL && phead->data != destval)
{
phead = phead->next;
}
if (phead == NULL)
{
return false;
}
//找到目标节点,把目标节点放在头结点后面
NewNode->next = phead->next;
phead->next = NewNode;
return true;
}
4. 删除节点
如图:

删除节点时,我们需要遍历链表,找到目标节点,并将前一个节点的 next 指向目标节点的下一个节点,然后释放目标节点的内存。
/**
* @name LinkedList_DellInsert
* @brief 删除
* @param head 头节点地址
* @param data 数据域
* @return
* @retval true 删除成功
* @retval true 删除失败
* @date 2025/04/09
* @version 1.0
* @note
*/
bool LinkedList_Del(LinkedList_t *head, DataType_t dest)
{
//判断链表是否为空
if (head->next == NULL)
{
return false;
}
//备份头结点和当前节点的地址
LinkedList_t *phead = head;
LinkedList_t *current = head->next;
while(current != NULL)
{
//如果当前节点的值等于目标值,则删除
if (current->data == dest)
{
//让头节点跳过当前节点
phead->next = current->next;
free(current);
return true;//删除成功
}
//头结点后移
phead = current;
//当前节点后移
current = current->next;
}
return false;
}
5. 查找节点
我们可以遍历链表来查找目标数据的节点,并返回找到的节点的指针。如果找不到,返回 NULL。
/**
* @name LinkedList_Search
* @brief 查找链表中指定数据的节点
* @param head 链表头指针
* @param dest 要查找的数据
* @return
* @retval LinkedList_t* 成功返回节点指针
* @retval NULL 失败返回NULL
* @date 2025/04/09
* @version 1.0
* @note
*/
LinkedList_t *LinkedList_Search(LinkedList_t *head, DataType_t dest) {
// 参数有效性检查
if (head == NULL || head->next == NULL) {
return NULL; // 空链表直接返回NULL
}
// 从头结点的第一个有效节点开始遍历
LinkedList_t *current = head->next;
// 顺序遍历链表
while (current != NULL) {
// 找到数据匹配的节点
if (current->data == dest) {
return current; // 返回找到的节点指针
}
current = current->next; // 继续检查下一个节点
}
// 遍历完仍未找到目标值
return NULL;
}
6. 修改节点
更新节点的操作是查找目标节点后,直接修改其数据域。
/**
* @name LinkedList_Update
* @brief 修改链表中指定数据的节点
* @param head 链表头指针
* @param oldVal 旧数据
* @param newVal 新数据
* @return
* @retval true 修改成功
* @retval false 修改失败
* @date 2025/04/09
* @version 1.0
* @note
*/
bool LinkedList_Update(LinkedList_t *head, DataType_t oldVal, DataType_t newVal) {
// 参数有效性检查
if (head == NULL || head->next == NULL)
{
return false; // 空链表直接返回失败
}
// 从头结点的下一个节点开始遍历
LinkedList_t *current = head->next;
// 遍历链表查找目标值
while (current != NULL) {
// 找到匹配的节点
if (current->data == oldVal) {
current->data = newVal; // 修改数据域
return true; // 返回修改成功
}
current = current->next; // 继续检查下一个节点
}
// 遍历完仍未找到目标值
return false;
}
7. 遍历链表
为了查看链表的内容,我们实现了一个打印函数,遍历整个链表并输出每个节点的数据。
/**
* @name LinkedList_DellInsert
* @brief 遍历链表
* @param head 头节点地址
* @return
* @retval
* @date 2025/04/09
* @version 1.0
* @note
*/
void LinkedList_Print(LinkedList_t *head) {
if (head == NULL) {
printf("链表不存在\n");
return;
}
LinkedList_t *current = head->next; // 跳过头节点
printf("当前链表:");
while(current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
8.测试及结果
int main() {
// 1. 创建链表 ====================================
printf("1. 创建链表...\n");
LinkedList_t *list = LinkedList_Create();
if (list == NULL) {
printf("链表创建失败!\n");
return -1;
}
printf("创建成功\n");
// 2. 插入测试 ====================================
printf("2. 插入测试...\n");
// 头部插入
printf("头部插入10, 20, 30\n");
LinkedList_HeadInsert(list, 10);
LinkedList_HeadInsert(list, 20);
LinkedList_HeadInsert(list, 30);
// 尾部插入
printf("尾部插入40, 50\n");
LinkedList_TailInsert(list, 40);
LinkedList_TailInsert(list, 50);
// 打印当前链表
printf("当前链表:");
LinkedList_Print(list); // 预期:30 -> 20 -> 10 -> 40 -> 50 -> NULL
printf("\n");
// 3. 查找测试 ====================================
printf("3. 查找测试...\n");
DataType_t search_val = 10;
LinkedList_t *found = LinkedList_Search(list, search_val);
if (found) {
printf("找到值%d,节点地址:%p\n", search_val, (void*)found);
} else {
printf("未找到值%d\n", search_val);
}
printf("\n");
// 4. 修改测试 ====================================
printf("4. 修改测试...\n");
DataType_t old_val = 10, new_val = 100;
if (LinkedList_Update(list, old_val, new_val)) {
printf("修改成功:%d -> %d\n", old_val, new_val);
} else {
printf("修改失败:未找到%d\n", old_val);
}
printf("修改后链表:");
LinkedList_Print(list); // 预期:30 -> 20 -> 100 -> 40 -> 50 -> NULL
printf("\n");
// 5. 删除测试 ====================================
printf("5. 删除测试...\n");
DataType_t del_val = 20;
if (LinkedList_Del(list, del_val)) {
printf("删除成功:%d\n", del_val);
} else {
printf("删除失败:未找到%d\n", del_val);
}
printf("删除后链表:");
LinkedList_Print(list); // 预期:30 -> 100 -> 40 -> 50 -> NULL
printf("\n");
return 0;
}
结果
1. 创建链表...
创建成功2. 插入测试...
头部插入10, 20, 30
尾部插入40, 50
当前链表:当前链表:30 -> 20 -> 10 -> 40 -> 50 -> NULL
3. 查找测试...
找到值10,节点地址:0x5a73654456d0
4. 修改测试...
修改成功:10 -> 100
修改后链表:当前链表:30 -> 20 -> 100 -> 40 -> 50 -> NULL
5. 删除测试...
删除成功:20
删除后链表:当前链表:30 -> 100 -> 40 -> 50 -> NULL
在这篇文章中,我们实现了一个基础的单向链表,包括链表的创建、节点插入、删除、查找、更新和打印等操作。链表是一个动态数据结构,灵活性很强,适用于许多需要频繁插入和删除数据的场景。
希望这篇文章能帮助你理解和实现单向链表的基本操作!

浙公网安备 33010602011771号