C语言中数据结构——带头双向循环链表

news2024/10/6 12:22:42

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,数据结构

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

🐰带头双向循环链表

🏡概念

🏡链表结构体的定义 

🏡链表为空的判断

🏡链表节点的创建

🏡链表的初始化

🏡链表的打印

🏡链表的尾插

🏡链表的头插

🏡链表的尾删

🏡链表的头删

🏡链表的查找

🏡链表中在pos之前插入

🏡删除pos的值

🏡链表的销毁

🏡链表为什么使用的是一级指针

🌸(1)单链表(非头单向不循环连链表)使用二级指针

🌸(2)带头双向循环连链表使用一级指针

🏡狡猾的面试官

🏡链表的源码

🌸main函数

         🌸test.h文件

🌸test.c文件


🐰带头双向循环链表

🏡概念

1.无头单向非循环的链表:结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,例如哈希桶、图的邻接表等等。这种结构在笔试面试中出现的比较多。(之前提到的单链表是一种无头单向非循环的链表。)

 

2.带头双向循环链表:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构最燃复杂,但是使用代码实现以后会发现结构会带来很多优势,实现起来反而简单。

🏡链表结构体的定义 

typedef int LTDatatype;
typedef struct ListNode
{
    struct ListNode* next;//保存下个节点的地址
    struct ListNode* prev;//保存上个节点的地址
    LTDatatype data;//存储的数据
}ListNode;

🏡链表为空的判断

bool ListNodeEmpty(ListNode* pphead)
{
    assert(pphead);
    return pphead->next==pphead;//哨兵位的下个节点的地址和哨兵位的地址相等则链表为空,这里返回true
}

🏡链表节点的创建

动态创建一个节点,将节点的prev指针置空,将节点next指针置空,值赋值为x(x是传进来的参数),然后返回这个节点的地址。

ListNode* BuyListNode(LTDatatype x)
{
    ListNode* newnode=(ListNode*)malloc(sizeof(ListNode));
    if(newnode==NULL)
    {
        perror("malloc fail!");
        return NULL;
    }
    newnode->next=NULL;
    newnode->prev=NULL;
    newnode->data=x;
    return newnode;
}

🏡链表的初始化

链表的初始化是创建一个哨兵位,哨兵位的数据值为-1。且初始化时,是将哨兵位的prev指针指向自己,哨兵位的next指向自己。返回这个哨兵位的地址

ListNode* ListNodeInit(ListNode* pphead)
{
    pphead=BuyListNode(-1);
    pphead->next=pphead;
    pphead->prev=pphead;
    return pphead;
}

🏡链表的打印

创建一个结构体指针cur,让cur指向哨兵位的下一个节点(链表的头节点),然后遍历整个链表并打印每个节点的数据值,当cur的值等于哨兵位的指针的值,说明遍历完成,结束打印。

void ListNodePrint(ListNode* pphead)
{
    assert(pphead);
    ListNode* cur=pphead->next;//从头节点开始打印
    while(cur!=pphead)//当cur的值等与哨兵位的值(这里的值是存放的节点的地址)相等纠结束打印,
    {
        printf("%d ",cur->data);
        cur=cur->next;
    }
    printf("\n");
    printf("print has done\n");
}

🏡链表的尾插

这里不管是头节点还是中间节点,都能完成尾插,但是单向不循环链表,得分头节点和中间节点,体现出双向循环链表在尾删的便利

void ListNodePushBack(ListNode* pphead,LTDatatype x)
{
    assert(pphead);
    ListNode* tail=pphead->prev;
    ListNode* newnode=BuyListNode(x);
    tail->next=newnode;
    newnode->prev=tail;
    newnode->next=pphead;
    pphead->prev=newnode;
}

🏡链表的头插

void ListNodePushFront(ListNode* pphead,LTDatatype x)
{
    //这里不管是头节点还是中间节点,都能完成头插,但是单向不循环链表,得分头节点和中间节点
    assert(pphead);
    ListNode* newnode=BuyListNode(x);
    newnode->next=pphead->next;//a
    pphead->next->prev=newnode;//b
    //这里的a b一定在c d的前面,不然pphead->next被改后,没有找到原来的头节点。
    //这里其实用慢指针去赋值,就可以不在意顺序了
    pphead->next=newnode;//c
    newnode->prev=pphead;//d
}

🏡链表的尾删

尾删的时候,要注意不要把哨兵位删除了,所以需要判断链表是否为空,如果为空不能删除

void ListNodePopBack(ListNode* pphead)
{
    assert(pphead);
    assert(!ListNodeEmpty(pphead));
    ListNode* tail=pphead->prev;
    ListNode* tailPrev=tail->prev;
    free(tail);
    tail=NULL;
    pphead->prev=tailPrev;
    tailPrev->next=pphead;
}

🏡链表的头删

void ListNodePopFront(ListNode* pphead)
{
    assert(pphead);
    assert(!ListNodeEmpty(pphead));

    ListNode* tail=pphead->next;
    ListNode* tailNext=tail->next;
    free(tail);
    tail=NULL;

    pphead->next=tailNext;
    tailNext->prev=pphead;
}

🏡链表的查找

根据给出的值,然后去遍历整个链表,如果链表中有与给出的值相匹配的节点,就返回这个节点的地址,如果没有返回空指针。

ListNode* ListNodeFind(ListNode* pphead,LTDatatype x)
{
    assert(pphead);
    ListNode* cur=pphead->next;
    while(cur!=pphead)
    {
        if(cur->data==x)
        {
            return cur;
        }
        cur=cur->next;
    }
    return NULL;
}

🏡链表中在pos之前插入

void ListNodeInsert(ListNode* pos,LTDatatype x)
{
    assert(pos);
    ListNode* newnode=BuyListNode(x);
    //这种方法的可读性高
    ListNode* prev=pos->prev;
    prev->next=newnode;
    newnode->prev=prev;
    newnode->next=pos;
    pos->prev=newnode;
    //少定义一个指针的方法,但一定要记住先修改pos的右边
//    pos->prev->next=newnode;
//    newnode->prev=pos->prev;
//    newnode->next=pos;
//    pos->prev=newnode;
}

🏡删除pos的值

void ListNodeErease(ListNode* pos)
{
    assert(pos);
    ListNode* posPrev=pos->prev;
    ListNode* posNext=pos->next;
    free(pos);
    posPrev->next=posNext;
    posNext->prev=posPrev;
}

🏡链表的销毁

利用快慢指针的方式,做到free掉每个节点

void ListNodeDestroy(ListNode* pphead)
{
    assert(pphead);
    ListNode* cur=pphead->next;
    ListNode* prev=cur->next;
    while(cur!=pphead)
    {
        free(cur);
        cur=prev;
        prev=prev->next;
    }
    free(pphead);
}

🏡链表为什么使用的是一级指针

🌸(1)单链表(非头单向不循环连链表)使用二级指针

我们在实现单链表(非头单向不循环连链表)使用的是二级指针。其实我们也可以使用一级指针,但前提是链表不能对头节点进行操作。因为我们在主函数创建的结构体指针plist,如果没有对plist指向的链表的头节点进行操作,在调用删除或插入等函数时,就可以通过形参指针去修改链表。如果对plist指向的链表的头节点进行操作,在调用删除或插入函数时,就不能保证操作正确性了。传给函数的是plist值,函数的形参指针无法对plist进行操作,要想要改变plist的值,只能传plist的地址并且函数形参指针必须是二级指针。

🌸(2)带头双向循环连链表使用一级指针

我们的带头双向循环连链表之所以使用一级指针,因为我们在进行删除或插入等操作时,plist始终指向哨兵位节点,在一系列操作中,哨兵位的位置没有改变。所以链表只需传plist的值且函数形参指针使用一级指针。

🏡狡猾的面试官

如果在面试时,面试官让你在10分钟以内写完一个链表,链表的功能包括:链表的打印,链表的头删,链表的尾删,链表的头插,链表的尾插,链表的任意位置删除,链表任意位置插入,链表销毁。对于我而言,就算我准备的有多么的充足,在面试这种紧张环境下,我根本不可能完成这种要求,也就是我将得不到这家公司的offer。但是我们冷静分析一下,链表面试没有规定是哪一种,链表的种类一共有8之多,我们应该首选带头双向循环链表,别看它结构复杂,但是实现的功能的逻辑简单(可以减少很多空指针的情况),我们选了带头双向循环链表之后,其实链表的任意位置删除和链表的头删,链表的尾删其实功能类似,链表的任意位置删除和链表的头插,链表的尾插实现的功能类似。所以我们只需要复用代码就行。

链表的尾插:

void ListNodePushBack(ListNode* pphead,LTDatatype x)
{
    //复用ListNodeInsert
    ListNodeInsert(pphead->prev, x);
}

链表的头插:

void ListNodePushFront(ListNode* pphead,LTDatatype x)
{  
    //复用ListNodeInsert
    ListNodeInsert(pphead->next, x);
}

链表的尾删:

void ListNodePopBack(ListNode* pphead)
{    
    //复用ListNodeErease
    ListNodeErease(pphead->prev);
}

链表的头删:

void ListNodePopFront(ListNode* pphead)
{
//    复用ListNodeErease
    ListNodeErease(pphead->next);
}

这样我们就可以在10分钟之内识破面试官的诡计

🏡链表的源码

🌸main函数

#include "test.h"
void test1(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushBack(plist, 1);
    ListNodePushBack(plist, 2);
    ListNodePushBack(plist, 3);
    ListNodePushBack(plist, 4);
    ListNodePrint(plist);
}
void test2(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushFront(plist, 1);
    ListNodePushFront(plist, 2);
    ListNodePushFront(plist, 3);
    ListNodePushFront(plist, 4);
    ListNodePrint(plist);
}
void test3(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushFront(plist, 1);
    ListNodePushFront(plist, 2);
    ListNodePushFront(plist, 3);
    ListNodePushFront(plist, 4);
    ListNodePrint(plist);
    ListNodePopBack(plist);//尾删
    ListNodePrint(plist);
    ListNodePopFront(plist);//头删
    ListNodePrint(plist);
    ListNodePopBack(plist);//尾删
    ListNodePrint(plist);
    ListNodePopFront(plist);//头删
    ListNodePrint(plist);
    //ListNodePopFront(plist);//头删
}
void test4(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushFront(plist, 1);
    ListNodePushFront(plist, 2);
    ListNodePushFront(plist, 3);
    ListNodePushFront(plist, 4);
    ListNodePrint(plist);
    ListNode* pos=ListNodeFind(plist, 3);
    ListNodeInsert(pos, 100);
    ListNodePrint(plist);
}
void test5(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushFront(plist, 1);
    ListNodePushFront(plist, 2);
    ListNodePushFront(plist, 3);
    ListNodePushFront(plist, 4);
    ListNodePrint(plist);
    ListNode* pos=ListNodeFind(plist, 3);
    ListNodeErease(pos);
    ListNodePrint(plist);
}
void test6(void)
{
    ListNode* plist=NULL;
    plist=ListNodeInit(plist);
    ListNodePushFront(plist, 1);
    ListNodePushFront(plist, 2);
    ListNodePushFront(plist, 3);
    ListNodePushFront(plist, 4);
    ListNodePrint(plist);
    ListNodeDestroy(plist);
}
int main()
{
    //test1();//头插
    //test2();//尾插
    //test3();//头删尾删
    //test4();//任意位置的插入
    test5();//任意位置的删除
    //test6();//销毁链表
    return 0;
}

🌸test.h文件

#ifndef test_h
#define test_h
#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#endif /* test_h */

typedef int LTDatatype;
typedef struct ListNode
{
    struct ListNode* next;
    struct ListNode* prev;
    LTDatatype data;
}ListNode;

//链表为空的判断
bool ListNodeEmpty(ListNode* pphead);
//链表的初始化
ListNode* ListNodeInit(ListNode* pphead);
//链表的打印
void ListNodePrint(ListNode* pphead);
//链表的尾插
void ListNodePushBack(ListNode* pphead,LTDatatype x);
//链表的头插
void ListNodePushFront(ListNode* pphead,LTDatatype x);
//链表的尾删
void ListNodePopBack(ListNode* pphead);
//链表的头删
void ListNodePopFront(ListNode* pphead);
//链表的查找
ListNode* ListNodeFind(ListNode* pphead,LTDatatype x);
//在pos之前插入
void ListNodeInsert(ListNode* pos,LTDatatype x);
//删除pos的值
void ListNodeErease(ListNode* pos);
//链表的销毁
void ListNodeDestroy(ListNode* pphead);

🌸test.c文件

#include "test.h"
bool ListNodeEmpty(ListNode* pphead)
{
    assert(pphead);
    return pphead->next==pphead;
}
ListNode* BuyListNode(LTDatatype x)
{
    ListNode* newnode=(ListNode*)malloc(sizeof(ListNode));
    if(newnode==NULL)
    {
        perror("malloc fail!");
        return NULL;
    }
    newnode->next=NULL;
    newnode->prev=NULL;
    newnode->data=x;
    return newnode;
}
ListNode* ListNodeInit(ListNode* pphead)
{
    pphead=BuyListNode(-1);
    pphead->next=pphead;
    pphead->prev=pphead;
    return pphead;
}
void ListNodePrint(ListNode* pphead)
{
    assert(pphead);
    ListNode* cur=pphead->next;//从头节点开始打印
    while(cur!=pphead)//当cur的值等与哨兵位的值(这里的值是存放的节点的地址)相等纠结束打印,
    {
        printf("%d ",cur->data);
        cur=cur->next;
    }
    printf("\n");
    printf("print has done\n");
}
void ListNodePushBack(ListNode* pphead,LTDatatype x)
{
//    //这里不管是头节点还是中间节点,都能完成尾插,但是单向不循环链表,得分头节点和中间节点
//    assert(pphead);
//    ListNode* tail=pphead->prev;
//    ListNode* newnode=BuyListNode(x);
//    tail->next=newnode;
//    newnode->prev=tail;
//    newnode->next=pphead;
//    pphead->prev=newnode;
    
    //复用ListNodeInsert
    ListNodeInsert(pphead->prev, x);
}
void ListNodePushFront(ListNode* pphead,LTDatatype x)
{
//    //这里不管是头节点还是中间节点,都能完成头插,但是单向不循环链表,得分头节点和中间节点
//    assert(pphead);
//    ListNode* newnode=BuyListNode(x);
//    newnode->next=pphead->next;//a
//    pphead->next->prev=newnode;//b
//    //这里的a b一定在c d的前面,不然pphead->next被改后,没有找到原来的头节点。
//    //这里其实用慢指针去赋值,就可以不在意顺序了
//    pphead->next=newnode;//c
//    newnode->prev=pphead;//d
    
    
    //复用ListNodeInsert
    ListNodeInsert(pphead->next, x);
}
void ListNodePopBack(ListNode* pphead)
{
//    assert(pphead);
//    assert(!ListNodeEmpty(pphead));
//    ListNode* tail=pphead->prev;
//    ListNode* tailPrev=tail->prev;
//    free(tail);
//    tail=NULL;
//    pphead->prev=tailPrev;
//    tailPrev->next=pphead;
    
    
    //复用ListNodeErease
    ListNodeErease(pphead->prev);
}
void ListNodePopFront(ListNode* pphead)
{
//    assert(pphead);
//    assert(!ListNodeEmpty(pphead));
//
//    ListNode* tail=pphead->next;
//    ListNode* tailNext=tail->next;
//    free(tail);
//    tail=NULL;
//
//    pphead->next=tailNext;
//    tailNext->prev=pphead;
//
//    复用ListNodeErease
    ListNodeErease(pphead->next);
}
ListNode* ListNodeFind(ListNode* pphead,LTDatatype x)
{
    assert(pphead);
    ListNode* cur=pphead->next;
    while(cur!=pphead)
    {
        if(cur->data==x)
        {
            return cur;
        }
        cur=cur->next;
    }
    return NULL;
}
void ListNodeInsert(ListNode* pos,LTDatatype x)
{
    assert(pos);
    ListNode* newnode=BuyListNode(x);
    //这种方法的可读性高
    ListNode* prev=pos->prev;
    prev->next=newnode;
    newnode->prev=prev;
    newnode->next=pos;
    pos->prev=newnode;
    //少定义一个指针的方法,但一定要记住先修改pos的右边
//    pos->prev->next=newnode;
//    newnode->prev=pos->prev;
//    newnode->next=pos;
//    pos->prev=newnode;
}
//ListNodeErease有一个缺陷,就是可能删除了哨兵位
void ListNodeErease(ListNode* pos)
{
    assert(pos);
    ListNode* posPrev=pos->prev;
    ListNode* posNext=pos->next;
    free(pos);
    posPrev->next=posNext;
    posNext->prev=posPrev;
}
void ListNodeDestroy(ListNode* pphead)
{
    assert(pphead);
    ListNode* cur=pphead->next;
    ListNode* prev=cur->next;
    while(cur!=pphead)
    {
        free(cur);
        cur=prev;
        prev=prev->next;
    }
    free(pphead);
}

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/514374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何删除打印机任务?三种快速删除打印机任务的方法

打印机用户可能会经常遇到添加错的打印任务&#xff0c;或是一不小心重复选择过多的打印任务&#xff0c;环保人人有责&#xff0c;杜绝纸张浪费&#xff0c;驱动人生就为大家带来快速删除打印任务的方法。 方法一&#xff1a;使用Windows自带的清理功能 在Windows操作系统中…

谷歌全线反击!PaLM 2部分性能已经超越GPT-4

ChatGPT横空出世&#xff0c;所有人都能够明确感知到AI的惊人潜力&#xff0c;瞬间改变了整个AI行业的节奏&#xff0c;不紧不慢的谷歌也开始紧张了。 ChatGPT舆论热潮仍未消退&#xff0c;红色警报又拉响 北京时间5月11日凌晨1点&#xff0c;Google I/O 2023开发者大会上发布…

C++ | 深拷贝和浅拷贝

C 深拷贝和浅拷贝 当类的函数成员存在__指针成员__时会产生深拷贝和浅拷贝和问题。 在进行对象拷贝时会使用默认拷贝构造函数&#xff0c;默认进行浅拷贝&#xff0c;即只会拷贝指针的值&#xff0c;新拷贝的指针与原来的指针指向同一内存&#xff1b; 浅拷贝带来的问题是&…

stm32裸机开发下利用MultiTimer多任务时间片询

stm32裸机开发下利用MultiTimer多任务时间片询 &#x1f4cc;MultiTimerGithub地址&#xff1a;https://github.com/0x1abin/MultiTimer ✨这是一个类似Arduino平台上的Ticker库&#xff0c;如需阅读懂源码&#xff0c;起码需要有链表知识的储备&#xff0c;如果仅仅只是拿来使…

docker学习笔记(二)

目录 启动Docker ​编辑 建立 Docker 用户 ​编辑 测试 Docker 是否正常工作 卸载Docker Docker镜像加速器配置 配置镜像 检查加速器是否生效 如何在Linux中的.json文件下保存并退出 如果我是使用vi操作进来的&#xff0c;我该如何保存并退出呢&#xff1f; 如何在Li…

3 步集成 Terraform + 极狐GitLab CI ,实现基础设施自动化管理

本文来自&#xff1a;极狐GitLab 开发者社区 作者&#xff1a;KaliArch 利用极狐GitLab CI 实现基础设施编排自动化后&#xff0c;用户就可以使用极狐GitLab 进行基础设施管理&#xff1a;提交基础设施变更后&#xff0c;会触发 MR 进行极狐GitLab CI 流水线执行&#xff0c;从…

电视盒子什么牌子好?数码小编分享2023热销电视盒子排行榜

整理私信的时候有朋友希望我能分享电视盒子排行榜&#xff0c;对不了解电视盒子什么牌子好的朋友们来说&#xff0c;选购电视盒子时很容易踩雷&#xff0c;因此我根据各大电商平台的销量数据整理了最新热销电视盒子排行榜&#xff0c;对电视盒子感兴趣的朋友们可以了解一下。 ●…

时序预测 | Python实现AR、ARMA、ARIMA时间序列预测

时序预测 | MATLAB实现VAR和GARCH时间序列预测 目录 时序预测 | MATLAB实现VAR和GARCH时间序列预测预测效果基本介绍模型原理程序设计参考资料预测效果 基本介绍 Python实现AR、ARMA、ARIMA时间序列预测 模型原理 AR、ARMA、ARIMA都是常用的时间序列预测方法,它们的主要区别在…

功能优化升级,添加更丰富的刷题练习设置|会员权益、答题后显示数据、切题按钮、进入小组必设昵称、小组统计筛选条件、考试排行榜、优化缓存机制

土著刷题微信小程序v1.14&#xff0c;主要是针对用户在使用期间提的一些优化建议&#xff0c;经过评估后进行优化升级的一次版本&#xff0c;下面将逐条介绍一下这一版的优化点。 01 会员权益页面 通过对用户提出的问题进行归纳总结&#xff0c;部分用户在购买VIP时&#xff0c…

【git】修改作者和提交者信息

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 修改git作者和提交者信息 1. 正文 1.1 前提 1.1.1 作者和提交者 作者就是我们git log看到的信息&#xff0c;如下&#xff1a; 其修改方式参考&…

linux应用编程

项目内容 开发板内部使用c语言调用硬件驱动实现各种测试功能&#xff0c;保存测试结果。 外部程序通过socket接口使用tcp协议与开发板通信进行信息传输&#xff0c; 最后使用python GUI构造一个界面按照测试顺序逐步显示出各个模块的测试结果 测试包括&#xff1a;485-232uart、…

(十四)地理数据库创建——进一步定义数据库②

&#xff08;十四&#xff09;地理数据库创建——进一步定义数据库② 目录&#xff08;接上篇&#xff09; &#xff08;十四&#xff09;地理数据库创建——进一步定义数据库② 4.创建注释类4.1建立注释类4.2产生连接要素注释 5.创建几何网络5.1几何网络概述5.2建立几何网络 6…

亿级大表毫秒关联,荔枝微课基于Apache Doris 统一实时数仓建设实践

本文导读&#xff1a; Apache Doris 助力荔枝微课构建了规范的、计算统一的实时数仓平台&#xff0c;目前 Apache Doris 已经支撑了荔枝微课内部 90% 以上的业务场景&#xff0c;整体可达到毫秒级的查询响应&#xff0c;数据时效性完成 T1 到分钟级的提升&#xff0c;开发效率更…

设备树的相关概念

.dts相当于.c DTS的源码文件 DTC工具相当于gcc编译器 将dts 编译成 dtb dtb相当于bin文件 或可执行文件 编译dtb 文件的方法 在linux内核文件夹中 make imx6ull-alientek-emmc.dtb在执行上述代码之前 要把 imx6ull-alientek-emmc.dtb删除 否则会提示已经存在 dts的结构 层层…

【Python文本处理】基于GPX文件的心率、速度、时间等参数更改

【Python文本处理】基于GPX文件的心率、速度、时间等参数更改 GPX文件本身其实就是坐标、海拔、时间、心率等综合性的xml文件 如图&#xff1a; 海拔&#xff1a;ele 时间&#xff1a;time 心率&#xff1a;heartrate 在不改变坐标和距离的情况下 缩短时间即可提高速度&#…

使用rsync和inotify实时备份CentOS服务器数据(详解)

简介 在日常运维中&#xff0c;确保服务器上的数据安全是至关重要的。数据丢失或损坏可能会导致灾难性后果&#xff0c;因此定期备份数据是一个明智的做法。本文LZ将向您展示如何使用 rsync 和 inotify-tools 工具在 CentOS 系统上设置实时备份&#xff0c;以确保您的数据始终…

Google I/O:谷歌AR看似不紧不慢,实则暗藏玄机

在今天举行的Google I/O大会上&#xff0c;尽管AI是全场最大的关注点&#xff0c;也还是有一系列AR相关技术和应用更新&#xff0c;比如&#xff1a;ARCore进行更新、推出新的Geospatial Creator等等。 ARCore面世已有5年时间&#xff0c;谷歌每年都在持续推动AR技术的发展。相…

Webpack 核心概念

文章目录 Webpack 核心概念概述安装webpack简单使用配置webpack.config.js配置package.json打包 核心概念modeentry 和 outputentry配置说明output配置说明配置单入口配置多入口 loader安装babel、babel-loader、core-js配置.babelrc配置webpack.config.js配置package.json编写…

【Linux】9. 习题①

2022-09-17_Linux环境与版本 1. 命令拓展(了解) linux查看cpu占用的命令是什么&#xff1f; A.top B.netstat C.free D.df 【答案解析】A 2022-09-24_Linux环境与版本 2. 命令拓展(了解) 以下哪个命令输出Linux内核的版本信息&#xff1a; A.uname -r B.vmstat C.sar D.sta…

wms仓库管理对企业的重要性

一、什么是wms仓库管理&#xff1f; WMS是仓库管理系统的缩写&#xff0c;它提供了一个可视化、智能化的平台&#xff0c;帮助公司进行仓库作业的管理和优化。WMS系统可以支持各种类型的仓库&#xff0c;包括零件、分销、批发、跨境电商等等&#xff0c;并提供多种功能&#xf…