前言
本次回顾链表的C语言实现是一方面
1.编程的手不能生
2.我现在干的是鸿蒙南向开发,我发现,在这里和原来的桌面客户端的编程习惯有些不一样,我们习惯用的就是常见的链表,linux嵌入式开发他们喜欢用的是一种侵入式链表,特此来专门回顾一下这方面的内容
常规链表
定义
这个相信大家都很熟了,其实就是一个节点结构体,包含存放的内容以及根据自己设计的链表的指针,我这里设计的节点,是内部存放一个int类型的数据意思下,每个节点有指向前一个节点的prev指针和一个指向后面的节点的next指针,我这里比较懒,就维护了一个头指针管理链表,早知道加一个尾指针和链表长度,性能可以进一步优化,具体实现直接贴代码吧,这个把概念理解了就很容易。
链表实现
#include"./CommonList.h"
#include <cstdio>
//#include <cstddef>
listNode *CreatList(int n){
listNode *node =new listNode;
node->prev=nullptr;
node->next=nullptr;
node->nodenum=n;
return node;
}
bool PushOneNodeToHead(int n,listNode **node){
if(!*node){
return false;
}
listNode *newhead =new listNode;
newhead->nodenum=n;
newhead->prev=nullptr;
newhead->next=*node;
(*node)->prev=newhead;
*node=newhead;//将头指针直接挪到新节点上
return true;
}
bool PushOneNodeToTail(int n,listNode *node){
if(!node){
return false;
}
listNode *newtail =new listNode;
while(node->next!=nullptr){
node=node->next;
}
newtail->nodenum=n;
newtail->prev=node;
newtail->next=nullptr;
node->next=newtail;
return true;
}
bool PushOneNodeToEverywhere(int n,listNode **node,int set){
if(!node){
return false;
}
if(set==0){
PushOneNodeToHead(n,node);
return true;
}
listNode* cur =*node;//要插入位置的前面的节点
listNode* tail =*node;//要插入位置的后面的节点
//异常处理,如果要插入的位置超出了链表的长度,直接不加了
while(set!=0){
if(set!=0&&cur->next==nullptr){
return false;
}
if(set == 1){
break;
}
cur=cur->next;
set--;
}
if(cur->next==nullptr){
PushOneNodeToTail(n, *node);
return true;
}
tail = cur->next;
listNode* temp = new listNode;
temp->nodenum = n;
temp->prev = cur;
temp->next = tail;
cur->next = temp;
tail->prev = temp;
return true;
}
bool DeleteOneNode(int where,listNode** node){
//分三种情况,要处理好,不然容易出现野指针的问题,删除的节点在链表头、链表中、链表尾,早知道设计的时候维护尾指针河链表长度了,偷下懒搞麻烦了,啊啊啊啊
//开始认真了,因为节点是new的要注意delete,别把内存搞炸了
//第一步,判断节点位置,三种位置处理不同!!!让指针走到预定位置
listNode* cur = *node;
if(where==0){
//链表只有一个节点的情况
if((*node)->next==nullptr){
delete *node;
*node = nullptr;//这里是把外部头指针置空
return true;
}
*node = (*node)->next;
(*node)->prev = nullptr;
delete cur;
return true;
}
while(where!=0){
//异常处理:防止删除节点越界
if(where!=0 && cur->next==nullptr){
return false;
}
cur = cur->next;
where--;
}
//如果删除的是尾节点,尾节点前面的节点的next需要指向nullptr
if(cur->next==nullptr){
listNode *temp = cur;
cur = cur->prev;
cur->next = nullptr;
delete temp;
return true;
}
//如果删除的是中间节点
cur->prev->next = cur->next;//简化写法,之前没想到,上面写的时候搞忘了
cur->next->prev = cur->prev;
delete cur;
return true;
}
bool DeleteList(listNode** node){
listNode* cur = *node;
while(cur!=nullptr){
listNode* temp = cur->next;
delete cur;
cur = temp;
}
*node = nullptr;
return true;
}
void PrintList(listNode* node){
if(!node){return;}
listNode* cur = node;
while(cur!=nullptr){
printf(" %d -> ",cur->nodenum);
cur = cur->next;
}
return;
}
函数解释
这个注释是不是一看就是高大上的感觉,只在大型项目才有机会看到这么完善的注释吧。
这个我是从海康的摄像头sdk学过来的,我看过的最舒服的注释
@brief 函数介绍
@param in 函数入参
@return 函数返回值
// 普通链表
#pragma once
#include<stdio.h>
// 链表结构体,C语言风格,这里就只存int类型,用不了模板
//此链表设计上只维护了头指针,没有维护尾部指针,这点可以优化
struct listNode {
listNode *prev; //前向指针,指向前一个节点
listNode *next;//后向指针,指向后面的节点
int nodenum;//节点内存的值
};
/**
*@brief 创建链表头节点并返回头节点地址
*@param [in]输入节点存的值
*@return 成功返回头节点地址,失败返回nullptr
*/
listNode *CreatList(int n);
/**
*@brief 在指定链表头部插入节点
*@param [in] n 插入节点存的值
*@param [in] node 要插入的链表地址
*@return 成功返回true,失败返回false
*/
bool PushOneNodeToHead(int n,listNode **node);
/**
*@brief 在指定链表的尾部插入节点
*@param [in] n 待插入节点的值
*@param [in] node 待插入节点的链表,因为不维护尾节点指针,所以需要遍历找到尾部加上
*@return 成功返回true,失败返回false
*/
bool PushOneNodeToTail(int n,listNode *node);
/**
*@brief 在指定的任意位置插入节点
*@param [in] 待插入节点的值
*@param [in] 待插入节点的链表
*@param [in] 待插入的位置下标(头节点为0)
*@return 成功返回true,失败返回false
*/
bool PushOneNodeToEverywhere(int n,listNode **node,int set);
/**
*@brief 删除指定位置的单个指针
*@param [in] where 待删除位置下标
*@param [in] node 待删除节点所在的链表
*@return 成功返回true,失败返回false
*/
bool DeleteOneNode(int where,listNode** node);
/**
*@brief 删除整个链表
*@param [in] 待删除的链表
*@return 成功返回true,失败返回false
*/
bool DeleteList(listNode** node);
/**
*@brief 打印整个链表
*@param [in] node 指向链表头节点的指针
*@return 直接遍历打印,不保存数据
*/
void PrintList(listNode* node);
测试用例
头文件
// List-Study.h: 标准系统包含文件的包含文件
// 或项目特定的包含文件。
#pragma once
#include"./CommonList.h"
enum CommonOperate{
CommonCreate,CommonInsertHead,CommonInsertTail,CommonInsertEverywhere,CommonDeleteOneNode,CommonDeleteAll,CommonPrint,CommonQuit
};
实现文件
// List-Study.cpp: 链表操作交互式测试程序
#include "List-Study.h"
#include <cstdio>
static void ShowMenu() {
printf("\n========== 链表操作测试 ==========\n");
printf("| %-2d. 创建链表 |\n", CommonCreate);
printf("| %-2d. 头部插入 |\n", CommonInsertHead);
printf("| %-2d. 尾部插入 |\n", CommonInsertTail);
printf("| %-2d. 任意位置插入 |\n", CommonInsertEverywhere);
printf("| %-2d. 删除节点 |\n", CommonDeleteOneNode);
printf("| %-2d. 删除链表 |\n", CommonDeleteAll);
printf("| %-2d. 打印链表 |\n", CommonPrint);
printf("| %-2d. 退出 |\n", 7);
printf("==================================\n");
printf("请选择: ");
}
int main() {
listNode* myList = nullptr;
int input;
while (true) {
ShowMenu();
if (scanf("%d", &input) != 1) break;
CommonOperate choice = (CommonOperate)input; // int 转枚举
switch (choice) {
case CommonCreate: {
if (myList) DeleteList(&myList);
int val;
printf("头节点值: ");
scanf("%d", &val);
myList = CreatList(val);
printf("创建成功!\n");
break;
}
case CommonInsertHead: {
if (!myList) { printf("请先创建链表!\n"); break; }
int val;
printf("插入值: ");
scanf("%d", &val);
PushOneNodeToHead(val, &myList);
printf("插入成功!当前链表: ");
PrintList(myList);
printf("\n");
break;
}
case CommonInsertTail: {
if (!myList) { printf("请先创建链表!\n"); break; }
int val;
printf("插入值: ");
scanf("%d", &val);
PushOneNodeToTail(val, myList);
printf("插入成功!当前链表: ");
PrintList(myList);
printf("\n");
break;
}
case CommonInsertEverywhere: {
if (!myList) { printf("请先创建链表!\n"); break; }
int val, pos;
printf("插入值: ");
scanf("%d", &val);
printf("位置(头节点=0): ");
scanf("%d", &pos);
if (PushOneNodeToEverywhere(val, &myList, pos))
printf("插入成功!当前链表: "), PrintList(myList), printf("\n");
else
printf("位置 %d 超出范围!\n", pos);
break;
}
case CommonDeleteOneNode: {
if (!myList) { printf("链表为空!\n"); break; }
int pos;
printf("删除位置(头节点=0): ");
scanf("%d", &pos);
if (DeleteOneNode(pos, &myList))
printf("删除成功!当前链表: "), PrintList(myList), printf("\n");
else
printf("位置 %d 超出范围!\n", pos);
break;
}
case CommonDeleteAll: {
if (!myList) { printf("链表已为空!\n"); break; }
DeleteList(&myList);
printf("链表已删除!\n");
break;
}
case CommonPrint: {
if (!myList) { printf("链表为空!\n"); break; }
printf("当前链表: ");
PrintList(myList);
printf("\n");
break;
}
case CommonQuit: {
if (myList) DeleteList(&myList); // ← 先释放内存
printf("退出程序。\n");
return 0;
}
default:
if (input == 0) {
if (myList) DeleteList(&myList);
printf("退出程序。\n");
return 0;
}
printf("无效选项!\n");
break;
}
}
return 0;
}
总结
这篇讲解了我对链表操作,指针的操作以及生命周期的一些领悟,如果是初学者把我写的看懂了,可以说这方面绝对能过关,因为双链表、单链表、循环链表都大差不差,顶多是指针的链接逻辑有些区别,循环链表说是循环的,那也只是逻辑上了循环,物理上区别不大。另外有一点疏忽的地方,刚开始实现的时候,不小心用了一些c++的东西混进去了,一下子想不起来是哪个位置了,呃呃呃呃