链表学习(常规链表)

前言

本次回顾链表的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++的东西混进去了,一下子想不起来是哪个位置了,呃呃呃呃

相关推荐
峥嵘life1 小时前
Android GMS 开机向导横竖屏切换配置总结
android·学习
剑挑星河月2 小时前
234. 回文链表
java·数据结构·算法·leetcode·链表
say_fall2 小时前
【Git 精品详解】企业规范:企业级开发模型、Git Flow、发版流程规范、Code Owner 制度、事故应急回滚
大数据·linux·服务器·git·学习·elasticsearch
kgduu2 小时前
react-redux学习笔记
笔记·学习·react.js
诗句藏于尽头2 小时前
Ai-3D音乐播放器-Mineradio -VibeCoding大赏-新增歌词字体自定义上传功能(个人学习)
学习
TE-茶叶蛋3 小时前
Node.js-Phase 1 学习总结:CLI 文件管理系统
学习·node.js
Waay11 小时前
面试口述版:个人对 Prometheus 完整理解
运维·学习·云原生·面试·职场和发展·kubernetes·prometheus
一楼的猫14 小时前
AI写作合规技术方案:平台检测机制分析与规避策略
人工智能·学习·机器学习·ai写作
四月天4315 小时前
web安全-SSTI(服务器模板注入)
笔记·学习·web安全·网络安全