当前位置: 首页 > news >正文

【数据结构】双链表

大家好!今天我们来学习数据结构中的双链表。(我们这里讲解的是带头(哨兵位)双向循环链表哦~)

目录

1.双链表的概念

2. 双链表的逻辑结构

3. 双链表的定义

4. 双链表的接口实现

4.1 动态申请一个新结点

4.2 双链表的初始化

4.3 打印双链表

4.4 尾插数据

4.5 尾删数据

4.6 头插数据

4.7 头删数据

4.8 获得双链表的长度

4.9 查找指定数据

4.10 在指定位置之前插入数据

4.11 删除指定位置

4.12 销毁双链表

5. 双链表的完整代码

5.1 List.h

5.2 List.c

5.3 Test.c

6. 顺序表和链表的区别

7. 总结


1.双链表的概念

双链表也叫双向链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双链表中的任意一个结点开始,都可以很方便地访问它的前驱结点后继结点

2. 双链表的逻辑结构

我们这里以带头双向循环链表为例,它的逻辑结构如下:

3. 双链表的定义

typedef int LTDataType;
typedef struct ListNode
{LTDataType data;  //存储的数据struct ListNode* prev;  //存放前一个结点的地址struct ListNode* next;  //存放后一个结点的地址
}LTNode;

使用结构体创建一个双链表。

SLTDataType替换int,方便对不同类型的数据进行修改。

SLTNode替换struct SListNode,方便简洁。

data是结点的数据域*prev用来存放前一个结点的地址(前驱)*next用来存放后一个结点的地址(后继)

4. 双链表的接口实现

双链表的所有接口函数一览:

//动态申请一个新结点
LTNode* BuyLTNode(LTDataType x);
//双链表的初始化
LTNode* LTInit();
//打印双链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//获得双链表的长度
int LTSize(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之前插入x
void LTInsert(LTNode* pos,LTDataType x);
//删除pos位置
void LTErase(LTNode* pos);
//销毁双链表
void LTDestroy(LTNode* phead);

这些接口函数主要实现了单链表的增删改查等功能,接下来我们一一实现这些函数!

4.1 动态申请一个新结点

我们每次给链表插入数据时,都需要动态开辟空间申请结点。所以我们将这个过程封装成函数,方便后续使用。

我们使用malloc()函数动态开辟一块空间表示新结点newnode,malloc函数返回一个void*类型的指针,指向分配的内存块的起始位置。如果内存分配失败,则返回一个空指针NULL。

所以我们要判断newnode是否为空指针NULL如果newnode是空指针,则用perror()函数打印相关错误,并用exit(-1)退出程序

如果newnode不为空,我们就用newnodedata赋值。又因为这是新开辟的结点,我们暂时将newnodeprevnewnodenext指向空

//动态申请一个新结点
LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = (LTNode *)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}

4.2 双链表的初始化

双链表的初始化,就是给双链表创建一个头结点。因为头结点(哨兵位)不存储有效数据,所以我们将头结点的data赋值为-1,同时让头结点的prev和next都指向自己,最后返回头结点的地址。

//双链表的初始化
LTNode* LTInit()
{LTNode* phead = BuyLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}

4.3 打印双链表

遍历双链表,依次打印双链表的元素。

我们定义一个结构体类型的指针cur,让cur一开始指向头结点的下一个结点(也就是哨兵位后面的一个结点)。当cur不为空时,输出cur指向的结点的值(cur->data),然后让cur指向下一个结点(cur=cur->next),依次进行,直到cur为头结点时停止(因为最后一个结点的next指针指向头结点)

//打印双链表
void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;printf("phead<=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}

4.4 尾插数据

尾插,就是创建一个新结点newnode,然后将newnode插入到尾结点tail的后面,让tail的next指向newnode,让newnode的prev指向tail;让newnode的next指向头结点phead头结点phead的prev指向newnode。建立这样的连接后,尾插就完成了

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

4.5 尾删数据

尾删,我们需要定义一个tailPrev存储尾结点tail的前一个结点(也就是tail->prev),再free掉tail,让tailPrev的next指向头结点phead,让头结点phead的prev指向tailPrev

这里要注意的是,如果链表为空(phead->next==phead),我们就不能进行尾删,所以我们要用assert()进行断言。

//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next!= phead); //链表为空的情况LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);tailPrev->next = phead;phead->prev = tailPrev;
}

4.6 头插数据

所谓头插,就是在双链表的头结点(哨兵位)后面的一个结点前插入数据。我们调用BuyLTNode()函数创建一个新结点newnode,让newnode的next指向头结点phead的next头结点phead的next的prev指向newnode;让头结点phead的next指向newnodenewnode的prev指向phead

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}

4.7 头删数据

头删,就是将头结点(哨兵位)后面的那一个结点删除。这里我们可以用first存储头结点(哨兵位)后面的第一个结点,用second存储哨兵位后面的第二个结点。然后free掉first。将头结点phead的next指向second而second的prev指向头结点

这里也要注意,如果链表为空(phead->next==phead),我们就不能进行头删,所以我们要用assert()进行断言。

​ 

//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);  //链表为空的情况LTNode* first = phead->next;LTNode* second = phead->next->next;free(first);phead->next = second;second->prev = phead;
}

4.8 获得双链表的长度

要获得双链表的长度,我们就使用cur从头结点(哨兵位)的后一个结点开始遍历,直到cur等于头结点phead时停止

//获得双链表的长度
int LTSize(LTNode* phead)
{assert(phead);int size = 0;LTNode* cur = phead->next;while (cur != phead){++size;cur = cur->next;}return size;
}

4.9 查找指定数据

定义一个结构体指针cur,让cur首先指向头结点(哨兵位)的下一个结点,然后遍历双链表,如果找到了指定数据cur->data==x),就直接返回cur。否则让cur指向cur->next,直到cur为头结点时停止。如果没有提前退出,完整完成了整个循环(也就是没有找到指定数据),就返回空指针NULL

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}

4.10 在指定位置之前插入数据

我们调用BuyLTNode()函数创建一个新结点newnode,定义一个结构体指针posPrev用来保存pos位置的前一个位置,让posPrev的next指向newnodenewnode的prev指向posPrev;让newnode的next指向pospos的prev指向newnode

既然我们要在指定位置之前插入数据,那么这个指定位置必须是存在的,所以我们要使用assert()断言。

//在pos位置之前插入x
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* newnode = BuyLTNode(x);posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode;
}

4.11 删除指定位置

我们要删除指定位置,可以定义一个结构体指针posPrev保存要删除位置的前一个位置,定义一个结构体指针posNext保存要删除位置的后一个位置。然后free掉posposPrev的next指向posNextposNext的prev指向posPrev

既然要删除指定位置,那么这个指定位置也必须是存在的,这里也同样要用assert()断言。

//删除pos位置
void LTErase(LTNode* pos)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;free(pos);posPrev->next = posNext;posNext->prev = posPrev;
}

4.12 销毁双链表

我们先让cur指向头结点(哨兵位)的下一个结点,然后遍历双链表,定义一个结构体指针next用来保存遍历时每一个结点的后面一个结点,依次free每个结点然后让cur指向next,直到cur指向头结点时停止

最后将头结点(哨兵位)phead释放

//销毁双链表
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

5. 双链表的完整代码

5.1 List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;  //存储的数据struct ListNode* prev;  //指向前一个结点的指针struct ListNode* next;  //指向后一个结点的指针
}LTNode;//动态申请一个新结点
LTNode* BuyLTNode(LTDataType x);
//双链表的初始化
LTNode* LTInit();
//打印双链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//获得双链表的长度
int LTSize(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之前插入x
void LTInsert(LTNode* pos,LTDataType x);
//删除pos位置
void LTErase(LTNode* pos);
//销毁双链表
void LTDestroy(LTNode* phead);

5.2 List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"//动态申请一个新结点
LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = (LTNode *)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc failed");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}//双链表的初始化
LTNode* LTInit()
{LTNode* phead = BuyLTNode(-1);phead->next = phead;phead->prev = phead;return phead;
}//打印双链表
void LTPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;printf("phead<=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next!= phead); //链表为空的情况LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;free(tail);tailPrev->next = phead;phead->prev = tailPrev;
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;newnode->prev = phead;
}//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);  //链表为空的情况LTNode* first = phead->next;LTNode* second = phead->next->next;free(first);phead->next = second;second->prev = phead;
}//获得双链表的长度
int LTSize(LTNode* phead)
{assert(phead);int size = 0;LTNode* cur = phead->next;while (cur != phead){++size;cur = cur->next;}return size;
}//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}//在pos位置之前插入x
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* newnode = BuyLTNode(x);posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode;
}//删除pos位置
void LTErase(LTNode* pos)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;free(pos);posPrev->next = posNext;posNext->prev = posPrev;
}//销毁双链表
void LTDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);
}

5.3 Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"void TestList()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 10);LTPushBack(plist, 10);LTPrint(plist);LTPopBack(plist);LTPopFront(plist);LTPrint(plist);LTPushFront(plist, 10);LTPushFront(plist, 20);LTPushFront(plist, 30);LTPushFront(plist, 40);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);LTDestroy(plist);plist = NULL;
}

6. 顺序表和链表的区别

存储器层次结构:

顺序表

优点:下标随机访问,cpu高速缓存命中率高

缺点:头部和中间插入删除效率低,扩容有一定程度性能消耗,可能存在一定程度的空间浪费。

链表

优点:可以任意位置插入删除,复杂度O(1),能够按需申请释放。

缺点:不支持下标随机访问。

7. 总结

到这里,我们就用C语言实现了数据结构中的双链表。有什么问题欢迎在评论区讨论。如果觉得文章有什么不足之处,可以在评论区留言。如果喜欢我的文章,可以点赞收藏哦!

相关文章:

【数据结构】双链表

大家好&#xff01;今天我们来学习数据结构中的双链表。&#xff08;我们这里讲解的是带头&#xff08;哨兵位&#xff09;双向循环链表哦~&#xff09; 目录 1.双链表的概念 2. 双链表的逻辑结构 3. 双链表的定义 4. 双链表的接口实现 4.1 动态申请一个新结点 4.2 双链表…...

android设置竖屏仍然跟随屏幕旋转怎么办

如题所问&#xff0c;我最近遇到一个bug&#xff0c;就是设置了摇感&#xff0c;然后有用户反馈说设置了手机下拉的系统设置-屏幕旋转-关闭。然后屏幕还是会旋转的问题。 首先&#xff0c;我们先从如何设置横竖屏了解下好了 设置横屏和竖屏的方法&#xff1a; 方法一&#x…...

java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展 tbms

​ 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以…...

【Java】2021 RoboCom 机器人开发者大赛-高职组(初赛)题解

7-1 机器人打招呼 机器人小白要来 RoboCom 参赛了&#xff0c;在赛场中遇到人要打个招呼。请你帮它设置好打招呼的这句话&#xff1a;“ni ye lai can jia RoboCom a?”。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在一行中输出 ni ye lai can jia Robo…...

汽车制造业上下游协作时 外发数据如何防泄露?

数据文件是制造业企业的核心竞争力&#xff0c;一旦发生数据外泄&#xff0c;就会给企业造成经济损失&#xff0c;严重的&#xff0c;可能会带来知识产权剽窃损害、名誉伤害等。汽车制造业&#xff0c;会涉及到重要的汽车设计图纸&#xff0c;像小米发送汽车设计图纸外泄事件并…...

H13-922题库 HCIP-GaussDB-OLAP V1.5

**H13-922 V1.5 GaussDB(DWS) OLAP题库 华为认证GaussDB OLAP数据库高级工程师HCIP-GaussDB-OLAP V1.0自2019年10月18日起&#xff0c;正式在中国区发布。当前版本V1.5 考试前提&#xff1a; 掌握基本的数据库基础知识、掌握数据仓库运维的基础知识、掌握基本Linux运维知识、…...

美团视觉GPU推理服务部署架构优化实战

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…...

什么是前端框架?怎么学习? - 易智编译EaseEditing

前端框架是一种用于开发Web应用程序界面的工具集合&#xff0c;它提供了一系列预定义的代码和结构&#xff0c;以简化开发过程并提高效率。 前端框架通常包括HTML、CSS和JavaScript的库和工具&#xff0c;用于构建交互式、动态和响应式的用户界面。 学习前端框架可以让您更高效…...

logstash 原理(含部署)

1、ES原理 原理 使⽤filebeat来上传⽇志数据&#xff0c;logstash进⾏⽇志收集与处理&#xff0c;elasticsearch作为⽇志存储与搜索引擎&#xff0c;最后使⽤kibana展现⽇志的可视化输出。所以不难发现&#xff0c;⽇志解析主要还 是logstash做的事情 从上图中可以看到&#x…...

CSS中的position属性有哪些值,并分别描述它们的作用。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ static⭐ relative⭐ absolute⭐ fixed⭐ sticky⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那…...

视频联网报警厂家怎么找?

视频联网报警厂家怎么找&#xff1f;要找到联网报警设备厂家&#xff0c;可以按照以下步骤进行&#xff1a; 1. 在互联网上搜索&#xff1a;可以使用搜索引擎&#xff0c;如谷歌或百度&#xff0c;搜索关键词&#xff0c;如“联网报警设备厂家”、“安防设备厂家”等&#xff…...

配置文件优先级解读

目录 概述 同级目录application配置文件优先级 application 以及bootstrap 优先级 不同级目录配置文件优先级 外部配置加载顺序 概述 SpringBoot除了支持properties格式的配置文件&#xff0c;还支持另外两种格式的配置文件。三种配置文件格式分别如下: properties格式…...

在 React+Typescript 项目环境中创建并使用组件

上文 ReactTypescript清理项目环境 我们将自己创建的项目环境 好好清理了一下 下面 我们来看组件的创建 组件化在这种数据响应式开发中肯定是非常重要的。 我们现在src下创建一个文件夹 叫 components 就用他专门来处理组件业务 然后 我们在下面创建一个 hello.tsx 注意 是t…...

UNIAPP中开发企业微信小程序

概述 需求为使用uni-app开发企业微信小程序。希望可以借助现成的uni-app框架&#xff0c;快速开发。遇到的问题是uni-app引入jweixin-1.2.0.js提示异常: Reason: TypeError: Cannot read properties of undefined (reading ‘title’)。本文中描述了如何解决该问题&#xff0c…...

NGINX负载均衡及LVS-DR负载均衡集群

目录 LVS-DR原理搭建过程nginx 负载均衡 LVS-DR原理 原理&#xff1a; 1. 当用户向负载均衡调度器&#xff08;Director Server&#xff09;发起请求&#xff0c;调度器将请求发往至内核空间 2. PREROUTING链首先会接收到用户请求&#xff0c;判断目标IP确定是本机IP&#xff…...

由于目标计算机积极拒绝,无法连接。 Could not connect to Redis at 127.0.0.1:6379

项目在启动时候报出redis连接异常 然后查看是redis 连接被计算机拒绝 解决方法 打开redis安装文件夹 先打开redis-servce.exe挂着&#xff0c;再打开redis-cli.exe 也不会弹出被拒接的问题了。而且此方法不用每次都去cmd里输入命令。...

电脑提示数据错误循环冗余检查怎么办?

有些时候&#xff0c;我们尝试在磁盘上创建分区或清理硬盘时&#xff0c;还可能会遇到这个问题&#xff1a;数据错误循环冗余检查。这是如何导致的呢&#xff1f;我们又该如何解决这个问题呢&#xff1f;下面我们就来了解一下。 导致冗余检查错误的原因有哪些&#xff1f; 数据…...

剑指offer62.圆圈中最后剩下的数字

这道题在算法课上的一个小故事上有一个类似的&#xff0c;就是一个军官打了败仗&#xff0c;带着他的几个兵逃到一个山洞&#xff0c;他们不想当俘虏想自杀&#xff0c;但是军官不想自杀但是又不好意思走&#xff0c;于是军官想了个办法&#xff0c;他们几个人围成一个圈&#…...

Python分享之 Spider

一、网络爬虫 网络爬虫又被称为网络蜘蛛&#xff0c;我们可以把互联网想象成一个蜘蛛网&#xff0c;每一个网站都是一个节点&#xff0c;我们可以使用一只蜘蛛去各个网页抓取我们想要的资源。举一个最简单的例子&#xff0c;你在百度和谷歌中输入‘Python&#xff0c;会有大量和…...

Golang项目中如何轻松实现私有仓库pkg包的引入

在企业内部创建一个公共的Golang模块工程可以帮助提高代码复用性和开发效率。本文将从如何创建一个公共的Golang工程开始&#xff0c;指导你一步步创建它、并引入到你的工程中。 1、公共模块规范 下面是一个简单的步骤指南来创建这样一个公共模块项目。 创建版本控制仓库&am…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

MySQL 主从同步异常处理

阅读原文&#xff1a;https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主&#xff0c;遇到的这个错误&#xff1a; Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一&#xff0c;通常表示&#xff…...

未授权访问事件频发,我们应当如何应对?

在当下&#xff0c;数据已成为企业和组织的核心资产&#xff0c;是推动业务发展、决策制定以及创新的关键驱动力。然而&#xff0c;未授权访问这一隐匿的安全威胁&#xff0c;正如同高悬的达摩克利斯之剑&#xff0c;时刻威胁着数据的安全&#xff0c;一旦触发&#xff0c;便可…...