C++——list模拟实现
目录
前言
一、list的结构
二、默认成员函数
构造函数
析构函数
clear
拷贝构造
赋值重载
swap
三、容量相关
empty
size
四、数据访问
front/back
五、普通迭代器
begin/end
六、const迭代器
begin/end
七、插入数据
insert
push_back
push_front
八、删除数据
erase
pop_back
pop_front
九、代码
总结
前言
我们之前讲解了string和vector的模拟实现,它们都是类似于顺序表的结构,string的底层是一个指针,一个元素的数量,一个空间总容量,而vector的底层是三个指针,这是它们结构上不一样的地方,但是实现的方法和原理还是大差不差的,那本篇我们来谈谈list,list的迭代器和之前的容器是不一样的,而且是一个双向带头循环链表,任意位置的插入删除效率都是O(1),那我们开始正题
一、list的结构
list的结构是一个一个的节点,所以我们在定义的时候就要去定义一个节点
template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;
};
_prev指向前一个节点
_next指向下一个节点
_val表示节点中存储的值
list_node这个类可以写成class,然后放成公有,也可以直接定义成struct的,因为这个类中的成员在list这个类中会用到,所以像这种节点类就直接放成公有就好
template<class T>
class list
{typedef list_node<T> Node;
private:Node* _head;size_t _size;
};
对于list这个类,我们需要先把list_node这个类给typedef,否则一直用他太长了,而且要带上模版参数,要注意的是,typedef也会受到访问限定符的限制,所以在外面是看不到Node的,然后我们用这个Node来定义一个头结点,也就是指向头节点的指针_head,然后还有一个成员函数是_size,_size是用来返回链表内的数据个数的,如果没有_size的话就只能遍历求一遍,这样增加一个成员变量代价反而小了
二、默认成员函数
构造函数
构造函数要来初始化成员变量,所以就需要先去给_head去new一个节点,然后它的前驱和后继都指向自己,_size初始化成0就好了,因为头节点不存储有效数据,所以也就不算做元素个数了
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
对于Node这个类在new的时候会去调用它的构造函数,所以我们要给Node类补上一个构造,用匿名对象做这里的缺省参数
template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}
};
析构函数
析构函数需要把所有的节点都释放了,因为所有节点都是new出来的,那就先来实现一个clear函数
clear
clear是要清空除头节点外链表中所有的节点,就要从头节点的下一个节点开始,去删除每一个节点,但是删除了当前节点就会找不到下一个节点,所以要先保存一下下一个节点
void clear()
{Node* cur = _head->_next;while (cur != _head){Node* next = cur->_next;delete cur;cur = next;}
}
cur表示的是当前节点,next记录下一个节点的位置,循环判断的条件就是不等于头结点就继续删,删到最后一个节点,那它的next就是头结点了,也就证明删完了,循环结束
那在析构函数中就先去调用clear先把后面链接的节点全部删掉,然后再把头节点删掉并置空就好了
~list()
{clear();delete _head;_head = nullptr;
}
拷贝构造
这里就不能再去用memcpy或者赋值来写了,因为list在物理上并不是连续的空间,不可以用下标来访问,所以采用的方法是遍历有数据的这个容器,然后依次尾插进被拷贝的容器中
//l2(l1)
list(const list<T>& l)
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto e : l){push_back(e);}
}
然后这里的l2在插入数据前一定要去初始化,要把头节点new出来再把前后都指向自己,否则在访问时会出问题,这里用的是范围for,那就需要支持迭代器才可以,并且push_back也还没有实现,所以暂时这个拷贝构造是不能跑的,大家可以等下面的实现完再回来看
赋值重载
赋值就写一个现代写法去调用拷贝构造就可以,这个我们很详细的说过了,在这里就不再多赘述了
swap
void swap(list<T>& l)
{std::swap(_head, l._head);std::swap(_size, l._size);
}
//l2 = l1
list<T>& operator=(list<T> l)
{swap(l);return *this;
}
三、容量相关
empty
判空的逻辑是判断头节点的前驱和后继是不是指向自己,且元素个数是否为0
bool empty() const
{return _head->_next == _head&& _head->_prev == _head&& _size == 0;
}
size
size函数只需要返回_size的值就可以
size_t size() const
{return _size;
}
四、数据访问
front/back
front就是返回第一个节点里存储的值,back返回最后一个节点里的值,一定要注意是第一个存储有效数据的节点,所以是头结点的下一个节点,而不是头节点中存储的值
front和back各自分别有两个版本,一个普通版本,一个const版本,跟我们之前讲的一样,普通对象优先调用普通版本,如果没有普通版本再去调用const版本,const对象只能调用const版本
T& front()
{return _head->_next->_val;
}const T& front() const
{return _head->_next->_val;
}T& back()
{return _head->_prev->_val;
}const T& back() const
{return _head->_prev->_val;
}
五、普通迭代器
我们在最开始的时候说过,list的迭代器与前面的有些不一样,我们就来看看list的迭代器有哪些不一样,可以说list最重要的部分就是迭代器,大家先来看可不可以这么写我们的迭代器呢?
typedef Node* iterator
答案显而易见,肯定是不可以的,因为迭代器我们解引用是要拿到数据,但是现在解引用拿到的是一个节点,而且++要向下一个位置走,--要向前一个位置走,现在这个迭代器++走到哪去谁能知道呢?所以我们要用运算符重载来控制这里的逻辑,也就要再封装一个迭代器的类来控制
template<class T>
struct __list_iterator
{typedef list_node<T> Node;Node* _node;
};
在外面需要去访问这个类中的成员函数,所以我们把这个迭代器类定义成struct的,里面有一个节点的指针_node,我们先来实现下*和->,*就是返回数据的引用,->是返回数据的地址
T& operator*()
{return _node->_val;
}T* operator->()
{return &(_node->_val);
}
然后是++和--,++就是向下一个节点走,--就是向前一个节点走,而++和--呢又分为前置和后置,我们先实现前置,前置的话就是_node走完返回迭代器的本身,那这个迭代器类的名字太长了,我们也同样来typedef一下
typedef __list_iterator<T> Self;
这个typedef就和Node的typedef放在一起就行
Self& operator++()
{_node = _node->_next;return *this;
} Self& operator--()
{_node = _node->_prev;return *this;
}
前置++返回的是迭代器走完的结果,所以就可以用引用返回
后置是返回++或者--之前的值,那就需要先保存一份才可以,然后返回保存的这一份,区分前置和后置,就是给后置加上一个int类型的参数
Self operator++(int)
{Self tmp(*this);_node = _node->_next;return tmp;
}Self operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;
}
迭代器判断循环结束的条件还有一个不等于,我们来实现一下,就是比较两个指针一不一样就可以了
bool operator!=(const Self& s)
{return _node != s._node;
}bool operator==(const Self& s)
{return _node == s._node;
}
begin/end
begin就是返回头节点的下一个位置,因为迭代器是左闭右开,所以end应该返回的是最后一个有效数据的下一个位置,也就是头节点_head
iterator begin()
{return _head->_next;
}iterator end()
{return _head;
}
那在list这个类中需要用到iterator,我们就把这个类给嵌到list这个类里,这个要放成公有的,外面要用迭代器
public:typedef __list_iterator<T> iterator;
而且还要给迭代器类实现一个构造函数,用节点指针去构造迭代器,现在的begin和end是隐式类型转换,构造加拷贝构造,拷贝构造我们不需要实现,迭代器去浅拷贝就可以了,也可以返回匿名对象或者是先构造出来对象再返回这个对象,这三种都可以,我们这种是最简单的,直接隐式类型的转换
__list_iterator(Node* node):_node(node)
{}
我们再结合迭代器的使用来看
int main()
{hx::list<int> l;hx::list<int>::iterator it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
需要什么我们就写什么就可以了,迭代器也就是需要这几个函数,不需要实现其他功能了,那我们的普通迭代器就实现好了
六、const迭代器
下面我们来看一下const迭代器,有了刚才的封装,那const迭代器可不可以直接套上一个const呢?
typedef __list_iterator<T> iterator;
typedef const __list_iterator<T> const_iterator;
这样写也是不行的,因为这样写就是修饰迭代器本身不能修改了,那迭代器本身都不能修改了还怎么++/--呢?所以const迭代器也是需要我们自己去封装一个自定义类型来控制,那我们就照葫芦画瓢来实现一个
template<class T>
struct __list_const_iterator
{typedef __list_const_iterator<T> Self;typedef list_node<T> Node;Node* _node;__list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_val;}const T* operator->(){return &(_node->_val);}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};
那大家有没有发现,普通迭代器和const迭代器不一样的地方就在于*和->的返回值,其他的地方全都一样,那这样设计就太冗余了,我们有没有办法可以就用一个类来控制一下*和->的返回值呢?其实很好办,只需要增加模版参数就可以
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef __list_iterator<T, Ref, Ptr> Self;typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &(_node->_val);}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};
Ref是引用,也就是operator*的返回值,Ptr是指针,也就是operator->的返回值,然后不要忘记在typedef Self的时候把这两个模版参数给加上,然后在list类中我们把后面的两个模版参数写死就可以了
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
begin/end
const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}
begin和end还是一样的实现逻辑,返回值变一样,再设计成const成员函数就可以
那迭代器部分我们就说到这里,因为这里可能会比较复杂,三个类互相缠绕,所以在文章的最后会把所有的代码给贴出来,如果大家对这里还有不太懂的地方可以去参考
七、插入数据
insert
insert是在某个迭代器插入一个val,那就是把这个迭代器里存的节点指针定义出来,然后找到前一个,再新创建一个节点,把他们之间互相链接起来就行了,最后返回新插入的这个节点的迭代器
iterator insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;
}
push_back
push_back是尾插,直接去复用insert,给一个迭代器位置
void push_back(const T& val)
{insert(end(), val);
}
end就是头结点,那在头节点的前面插入也就是尾插
push_front
push_back是头插,直接去复用insert,给一个迭代器位置
void push_front(const T& val)
{insert(begin(), val);
}
begin就是头结点的下一个位置,在begin之前插入,也就是在头结点和第一个节点之间插入,就是头插
八、删除数据
erase
erase是要删除某个迭代器位置,那也是把迭代器里存的节点指针定义出来,然后找到前后节点,把前后节点互相连起来就好了,最后再删除,然后要返回删除的这个位置的下一个节点的迭代器
iterator erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;
}
pop_back
pop_back是尾删,直接去复用erase,给一个迭代器位置
void pop_back()
{erase(--end());
}
end是头节点的位置,那--end就是头结点的前一个,也就是最后一个节点
pop_front
pop_front是头删,直接去复用erase,给一个迭代器位置
void pop_front(){erase(begin());}
begin是头结点的下一个位置,也就是第一个存放有效数据的节点
九、代码
namespace hx
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _val(val){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef __list_iterator<T, Ref, Ptr> Self;typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &(_node->_val);}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}};template<class T>class list{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}list(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}//l2(l1)list(const list<T>& l){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto e : l){push_back(e);}}//l2 = l1list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete _head;_head = nullptr;}void swap(list<T>& l){std::swap(_head, l._head);std::swap(_size, l._size);}bool empty() const{return _head->_next == _head&& _head->_prev == _head&& _size == 0;}size_t size() const{return _size;}void clear(){Node* cur = _head->_next;while (cur != _head){Node* next = cur->_next;delete cur;cur = next;}}T& front(){return _head->_next->_val;}const T& front() const{return _head->_next->_val;}T& back(){return _head->_prev->_val;}const T& back() const{return _head->_prev->_val;}iterator insert(iterator pos, const T& val){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}private:Node* _head;size_t _size;};
}
总结
本篇文章我们讲了list,以及关于迭代器部分,list的迭代器类型是一个自定义类型,跟以前不太一样,但是这种方式在以前也还是很常见的,比如map/set,unordered map/unordered set,还是要这么用,迭代器部分都要去这样封装,大家还是要习惯这种方式,那本篇文章就到这里啦,如果觉得下篇写的不错的小伙伴,可以给小编一个一键三连表示支持,感谢大家!!!
相关文章:

C++——list模拟实现
目录 前言 一、list的结构 二、默认成员函数 构造函数 析构函数 clear 拷贝构造 赋值重载 swap 三、容量相关 empty size 四、数据访问 front/back 五、普通迭代器 begin/end 六、const迭代器 begin/end 七、插入数据 insert push_back push_front 八、…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-utils.py
utils.py ultralytics\data\utils.py 目录 utils.py 1.所需的库和模块 2.def img2label_paths(img_paths): 3.def get_hash(paths): 4.def exif_size(img: Image.Image): 5.def verify_image(args): 6.def verify_image_label(args): 7.def visualize_image_ann…...
Linux 内核 RDMA CM 模块分析:drivers/infiniband/core/cma.c
一、引言 随着高性能计算和大数据处理需求的不断增长,远程直接内存访问(RDMA)技术在数据中心和高性能计算领域得到了广泛应用。RDMA 允许数据直接在不同系统的内存之间传输,而无需经过 CPU 和操作系统的干预,从而显著提高了数据传输效率和系统性能。Linux 内核中的 RDMA …...
Flask flash() 消息示例
目录 安装 Flask 入门:Flask flash() 基本示例 进阶:使用 Flask-WTF Flash 登录结果消息 详解:get_flashed_messages() 详解:flash() 消息的完整生命周期 Flask 提供 flash() 用于向 用户传递临时消息,通常用于: • 表单提交成功或失败 • 用户登录、注册、退出提…...

ImGui 学习笔记(三)—— 隐藏主窗口窗口关闭检测
ImGui 的主窗口是平台窗口,默认是可见的,这会影响视觉效果。那么怎么隐藏 ImGui 的主窗口呢? 这很简单,但是需要针对后端做一些修改。 本文仅介绍在 glfwopengl3 和 win32dx11 两种实现上如何修改。 在 win32dx11 实现上&#…...
ubuntu磁盘清理垃圾文件
大头文件排查 #先查看是否是内存满了,USER 很高即是满了 du -f#抓大头思想,优先删除大文件#查看文件目录 内存占用量并排序,不断文件递归下去 du --max-depth1 -h /home/ -h | sort du --max-depth1 -h /home/big/ -h | sort 缓存文件清理…...

vue-fastapi-admin 部署心得
vue-fastapi-admin 部署心得 这两天需要搭建一个后台管理系统,找来找去 vue-fastapi-admin 这个开源后台管理框架刚好和我的技术栈所契合。于是就浅浅的研究了一下。 主要是记录如何基于原项目提供的Dockerfile进行调整,那项目文件放在容器外部…...

大语言模型微调的公开JSON数据
大语言模型微调的公开JSON数据 以下是一些可用于大语言模型微调的公开JSON数据及地址: EmoLLM数据集 介绍:EmoLLM是一系列能够支持理解用户、帮助用户心理健康辅导链路的心理健康大模型,其开源了数据集、微调方法、训练方法及脚本等。数据集按用处分为general和role-play两种…...
C++STL容器之set
1.介绍 set容器是C标准模板库(STL)中的一个关联容器,用于存储唯一的元素。set中的元素是自动排序的,不允许重复。set通常基于红黑树(一种自平衡二叉查找树)实现,因此插入、删除和查找操作的时间…...

《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成
量子计算的神秘面纱 在科技飞速发展的今天,量子计算作为前沿领域,正逐渐走进大众的视野。它宛如一把神秘的钥匙,有望开启未来科技变革的大门,而微软量子芯片则是这把钥匙上一颗璀璨的明珠。 量子计算,简单来说,是一种遵循量子力学规律调控量子信息单元进行计算的新型计算…...

使用AI创建流程图和图表的 3 种简单方法
你可能已经尝试过使用 LLMs 生成图像,但你有没有想过用它们来创建 流程图和图表?这些可视化工具对于展示流程、工作流和系统架构至关重要。 通常,在在线工具上手动绘制图表可能会耗费大量时间。但你知道吗?你可以使用 LLMs 通过简…...

从波士顿动力到Figure AI:探寻人工智能驱动的机器人智能化
一、引言 1.1 研究背景与意义 在科技飞速发展的当下,机器人智能化已成为全球科技竞争的关键领域,深刻影响着人类社会的生产与生活方式。从工业制造到日常生活服务,从医疗保健到探索未知领域,机器人正逐步渗透进各个行业,展现出巨大的发展潜力与应用价值。其智能化水平的…...
算法——KMP算法(Knuth-Morris-Pratt算法)
KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,用于在主文本字符串中快速查找模式字符串的出现位置。其核心思想是通过预处理模式字符串,利用部分匹配信息(即“失败函数”或“next数组”)避免…...

一周学会Flask3 Python Web开发-flask3模块化blueprint配置
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候,多多少少会划分几个或者几十个业务模块,如果把这些模块的视图方法都写在app.py…...
Pytorch实现之统计全局信息的轻量级EGAN
简介 简介:模型在EGAN的基础上改进了一个降维的自注意力机制,并且设计了一个新颖的选择算子,使用轮盘赌来选择个体,如果他们的适配度满足fchild<VALUE,则被选中的个体将被丢弃。需要在进化的初始阶段尽快找到最佳个体,并在后续阶段保持种群的多样性。 论文题目:LGE…...

Java开发实习面试笔试题(含答案)
在广州一家中大公司面试(BOSS标注是1000-9999人,薪资2-3k),招聘上写着Java开发,基本没有标注前端要求,但是到场知道是前后端分离人不分离。开始先让你做笔试(12道问答4道SQL题)&…...
《论模型驱动架构设计方法及其应用》审题技巧 - 系统架构设计师
软件测试工程师软考论文写作框架 一、考点概述 “模型驱动架构设计及其应用”这一论题,主要考察了考生对模型驱动架构设计(MDA)这一先进软件设计方法的理解与应用能力。论题涵盖了MDA的基本概念、核心要素、实施流程及在实际项目中的应用等…...

【服务器与本地互传文件】远端服务器的Linux系统 和 本地Windows系统 互传文件
rz 命令:本地上传到远端 rz 命令:用于从本地主机上传文件到远程服务器 rz 是一个用于在 Linux 系统中通过 串口 或 SSH 上传文件的命令,它实际上是 lrzsz 工具包中的一个命令。rz 命令可以调用一个图形化的上传窗口,方便用户从本…...

初学者如何设置以及使用富文本编辑器[eclipse版]
手把手教你设置富文本编辑器 参考来源:UEditor Docs 初学者按我的步骤来就可以啦 一、设置ueditor编辑器 1.提取文件[文章最底部有链接提取方式] 2.解压文件并放到自己项目中,在WebContent目录下: 3. 修改jar包位置路径 到--> 注意&a…...
在 Java 中解析 JSON 数据
例子解析以下JSON数据 {"code":0,"msg":"成功","data": [{ "host":"1068222.com", "port":"", "m_token":"490e20e70e7de5f21a24b14c12a393f6", "categ…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...