list【2】模拟实现(含迭代器实现超详解哦)

模拟实现list

  • 引言(实现概述)
  • list迭代器实现
    • 默认成员函数
    • operator* 与 operator->
    • operator++ 与 operator--
    • operator== 与 operator!=
    • 迭代器实现概览
  • list主要接口实现
    • 默认成员函数
      • 构造函数
      • 析构函数
      • 赋值重载
    • 迭代器
    • 容量
    • 元素访问
    • 数据修改
      • insert
      • erase
      • push_back 与 push_front
      • pop_back 与 pop_front
      • clear
      • swap
  • 源码概览
  • 总结

引言(实现概述)

在前面,我们介绍了list的使用:
戳我看list的介绍与使用详解哦

在本篇文章中将重点介绍list的接口实现,通过模拟实现可以更深入的理解与使用list
在这里插入图片描述
我们模拟实现的 list 底层是一个带头双向循环链表

在实现list时,我们首先需要一个结构体以表示链表中结点的结构list_node,大致包括数据与指向前后结点的指针

template<class T>
struct list_node  //结点类
{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}
};

在有了结点之后,还需要一个 list类,包括双向链表的头结点指针_pHead,链表中的元素个数_size

template<class T>
class list  //带头双向循环链表
{typedef list_node<T> Node;
private:Node* _pHead;size_t _size;
};

大致结构如下:
在这里插入图片描述
关于带头双向循环链表的实现可以参考之前的C语言版本实现,在本篇文章中结构将不是最重点的内容:戳我看C语言实现带头双向循环链表详解哦

与vector相同,list是一个类模板,其声明与定义不能分离。我们将模拟实现的list放在我们创建的命名空间内,以防止与库发生命名冲突。
在list的模拟实现中,我们只实现一些主要的接口,包括默认成员函数、迭代器、容量、元素访问与数据修改

list迭代器实现

与vector不同,list的迭代器是指针的封装,通过运算符重载来实现原生指针的功能
我们可以通过封装结点的指针,即list_node<T>*,来实现迭代器:

默认成员函数

对于默认成员函数,其实我们只需要实现构造函数即可,这个__list_iterator类的属性只有一个结构体指针,并不存在类中申请的资源,所以编译器生成的析构函数与赋值运算符重载就够用了,不需要再实现了。

  1. 默认构造
    对于默认构造函数,我们可以使用缺省参数,即参数类型为结构体指针,缺省值为nullptr
    直接在初始化列表中用参数初始化类属性即可:
	__list_iterator(Node* pNode = nullptr):_pNode(pNode){}
  1. 拷贝构造
    对于拷贝构造,参数必须是类类型的引用,可以加上const修饰(我们可以在类中将类类型__list_iterator<T, Ref, Ptr>重命名为self,以方便书写)
    在函数中直接赋值即可:
	__list_iterator(const self& it){_pNode = it._pNode;}

operator* 与 operator->

迭代器使用时应该是与指针基本类似的,所以也需要重载*->

  1. 重载 *
    指针当然可以进行解引用的操作,指向容器中元素的指针。对这个指针进行解引用操作结果就应该是该指针指向的元素。对于list的迭代器而言,解引用该迭代器的结果就应该是结点中的_data元素的引用,类型为&T
    (我们可以为模板参数加上一个参数,即Ref,它表示T&
	Ref operator*(){return _pNode->_date;}
  1. 重载->
    T是自定义类型时,其指针还可以通过->直接访问到T类型对象_data中的元素,起指针作用的迭代器自然也需要实现这个功能
    但是对于这个运算符重载而言,它并不知道要返回T类型对象中的什么元素,所以这个operator->函数可以直接返回该迭代器对应的结点中的_data元素的指针,然后在调用的时候再通过->来访问其中元素:it->->a;(通过迭代器it访问T类型对象中的a元素)。
    但是,这样的形式访问又与指针的用法不一致,所以这里有一个特殊的规定,即规定需要使用it->a;的方式通过迭代器访问T类型对象中的元素,以获得与原生指针相同的使用方式
    (我们可以为模板参数加上一个参数,即Ptr,它表示T*
	Ptr operator->(){return &(_pNode->_date);}

operator++ 与 operator–

  1. 重载 ++
    ++操作即实现迭代器的指向向后移动一个元素,list的迭代器底层是一个结构体指针,所以只需要将当前_pNode->_next 赋值给_pNode即可
    ++分为前置++与后置++,在区别这两个的实现时,前面类和对象时已经详细介绍过了,即给后置++增加一个参数int,但是不传参,只用于区分前置与后置。
    另外后置++需要创建临时对象,在*this++后必须要返回临时对象而非引用:
	self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}
  1. 重载 --
    重载--与前面的++类似,即_pNode->_prev 赋值给_pNode即可
	self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}

operator== 与 operator!=

对于==!= 重载,即判断两个迭代器对象的属性是否相等即可

	bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}

迭代器实现概览

	template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};

list主要接口实现

在实现list之前,我们可以对一些较为麻烦的类型进行重命名以方便书写:

typedef list_node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

默认成员函数

构造函数

实现构造函数时,我们需要实现无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造

无参构造
由于我们模拟实现的list底层为带头双向循环链表,所以在无参构造时虽然没有在list中放入元素,但是还使需要先放入一个空结点作为头节点
创建头节点的操作在任何构造函数中都要先进行,所以我们将其封装为一个init_empty_list函数。这个初始化空list的函数需要new一个结点,然后使节点中的_prev_next都指向它自身,最后将_size赋值为0:

	void init_empty_list(){			_pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}

n个指定元素构造:
使用n个指定元素构造有两个参数,第一个就是int,第二个是const T&,缺省值为T()
函数中,首先调用init_empty_list构建一个头结点;
再循环,使用push_bake尾插n个指定元素value即可(push_back后面会实现):

	list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}

迭代器区间构造
是用迭代器区间构造函数是一个函数模板,可以使用任何容器的迭代器区间来构造list
函数中,首先调用init_empty_list构建一个头结点;
然后再循环,使用push_bake尾插first迭代器的解引用出的元素,当firstlast相等时出循环:

	template <class Iterator>list(Iterator first, Iterator last){init_empty_list();while (first != last){push_back(*first);++first;}}

拷贝构造:
拷贝构造函数的参数是一个const list<T>&
实现时,首先调用init_empty_list构建一个头结点;
然后范围for,使用push_bake将l中的元素逐一尾插到list中:

	list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}

析构函数

析构函数需要实现释放list中的资源。
首先复用clear,清空list中的元素。clear中会实现释放结点中的资源,后面部分会实现;
delete _pHead;,释放头节点中的资源,它会调用结点结构体的析构函数

	~list(){clear();delete _pHead;}

赋值重载

对于赋值运算符重载,我们直接使用新写法,即先使用参数l创建一个临时对象;
然后使用swap将临时对象与*this交换(后面会实现swap函数);
最后返回*this即可,创建的临时对象就会在函数栈帧销毁时自动释放

	list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}

迭代器

在前面已经实现了list的迭代器,它是结点指针的封装

这里暂时只实现beginend,关于反向迭代器的实现在后面会详细介绍。
begin返回首元素的地址,即头结点的下一个结点的地址
end返回尾元素下一个位置的地址,即头节点的地址,他们分别重载有const版本:

	iterator begin(){return iterator(_pHead->_next);}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(_pHead->_next);}const_iterator end() const{return const_iterator(_pHead);}

容量

在容器部分,由于list并没有容量的概念,所以我们只需要实现sizeempty即可;
在这里插入图片描述
我们在list的属性中,我们设置了_size,在插入元素时_size++,删除元素时_size--,所以这里只需要返回_size的值即可;
_size == 0时,list为空,empty返回true,否者返回false

	size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}

元素访问

由于list在任意位置访问元素的成本较高,就没有提供operator[]的接口,所以我们只需要实现frontback即可。分别返回首尾的元素,有普通对象与const对象两个重载版本:
在这里插入图片描述
在实现时,我们可以借助list的属性_pHead,即头结点的指针来访问首尾元素
我们模拟实现list的底层是带头双向循环链表,所以list中的第一个元素就是_pHead->_next指向的结点中的元素;list中的_pHead->_prev指向的结点中的元素

front只需要返回 _pHead->_next->_date 即可;
back返回_pHead->_prev->_date即可,返回值类型为T&,const版本就返回const T&即可:

	T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}

数据修改

在这里插入图片描述

insert

list 的结构使在任意位置插入数据的效率是较高的,只需要创建结点,再链接到pos位置前即可

在实现insert时,首先new一个结点,类型为Node,并用val初始化(这个Node类型是前面重命名后的类型);
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prev,以方便后续链接;
然后将pos结点的前一个结点与新结点链接,即newnodeprev链接;
再将pos结点与新结点链接,即newnodecur链接;

最后,更新_size,并返回新结点的迭代器:

	// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}

erase

erase实现时,只需要释放pos位置的结点,并链接剩余的结点即可:

首先assert判断list是否为空;
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prevpos的后一个结点指针为next,以方便后续链接;
然后直接链接pos位置的前一个结点与后一个结点,即链接prevnext即可;

最后,释放cur指向的结点,更新_size,并返回next

	// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}

push_back 与 push_front

对于头插与尾插的实现,复用insert即可:

push_front,即在首结点的前面一个位置插入一个元素,即begin()迭代器位置插入
push_back,即在尾结点的后一个位置插入一个元素,即end()位置插入

	void push_back(const T& val){insert(end(), val);}void push_front(const T& val){insert(begin(), val);}

pop_back 与 pop_front

对于头删尾删的实现,复用erase即可

pop_front,即删除头节点,即 erase删除begin()位置的结点 即可;
pop_back,删除尾结点,即 erase删除end()前面一个结点即可,但是由于list迭代器不支持-操作,所以这里传参为--end()

	void pop_front(){erase(begin());}void pop_back(){erase(--end());}

clear

clear用于清理list中的所有元素,可以直接复用erase来实现清理

我们可以通过遍历迭代器的方式逐一释放结点:
it的初始值为begin(),循环逐一erase删除,当it等于end()的时候终止循环,就可以实现删除所有结点并保留头节点;
另外,由于erase删除某节点后,会返回删除节点的下一个位置,所以只要把返回值载赋值给it就实现了迭代器的向后移动

	void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}

swap

实现list的交换函数时,只需要使用库swap交换list的属性即可,即交换_pHead_size

	void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}

源码概览

(关于反向迭代器的实现在后面会详细介绍,现在可以暂时忽略)

#include<iostream>
#include<cassert>
#include"my_reverse_iterator.h"namespace qqq
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};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;typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;public:/ constructor and destructor void init_empty_list(){			_pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){init_empty_list();while (first != last){push_back(*first);++first;}}list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}~list(){clear();delete _pHead;}// List Iterator///iterator begin(){return iterator(_pHead->_next);}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(_pHead->_next);}const_iterator end() const{return const_iterator(_pHead);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}/List Capacity//size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}///List Access///T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}List Modify//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());}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:Node* _pHead;size_t _size;};
};

总结

到此,关于list的模拟实现就到此结束了
模拟实现容器并不是为了造一个更好的轮子,而是为了更好的理解与使用容器

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

堆排序详解

堆&#xff1a;是一种特殊的完全二叉树&#xff0c;一般通过顺序表存储&#xff0c;分为大堆和小堆两类。 大堆&#xff1a;父节点的值恒大于子节点的值。 小堆&#xff1a;父节点的值恒小于子节点的值。 创建堆&#xff0c;可以使得根节点成为整个堆中保存最大或最小的值的…

基于jeecg-boot的flowable流程历史记录显示修改

更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 历…

一文搞定接口幂等性架构设计方案

幂等性介绍 现如今很多系统都会基于分布式或微服务思想完成对系统的架构设计。那么在这一个系统中&#xff0c;就会存在若干个微服务&#xff0c;而且服务间也会产生相互通信调用。那么既然产生了服务调用&#xff0c;就必然会存在服务调用延迟或失败的问题。当出现这种问题&a…

系列四、Nginx的常用命令和配置文件

一、常用命令 1.1、查看nginx的版本号 ./nginx -v 1.2、启动nginx cd /usr/local/nginx/sbin./nginx 1.3、停止nginx cd /usr/local/nginx/sbin./nginx -s stop 1.4、重新加载nginx 说明&#xff1a;该命令用于修改配置文件后&#xff0c;在不重启nginx的情况下使配置文…

FPGA通信—千兆网(UDP)软件设计

一、PHY引脚功能描述 引脚功能描述1CLK25 CLK125:内部PLL生成的125MHz参考时钟&#xff0c;如MAC未使用125MHe时钟&#xff0c;则此引脚应保持浮动&#xff0c; 2 4 63 GND 接地3REG OUT开关压器&#xff0c;1.05V输出 5 6 8 9 11 12 14 15 MDI[0] MDI[0]- MDI[1] MDI[1…

学习笔记-BNF、EBNF、ABNF语法格式描述规范

目标是确认一些c/cpp的语法细节&#xff0c;需要看cpp语法定义文件。 考虑从c的语法定义文件开始确认。 考虑实现一个简化的语言定义和编译器&#xff0c;为后续的实际需求做自定义扩展。 参考网页&#xff1a; https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_f…

高可用Kuberbetes部署Prometheus + Grafana

概述 阅读官方文档部署部署Prometheus Grafana GitHub - prometheus-operator/kube-prometheus at release-0.10 环境 步骤 下周官方github仓库 git clone https://github.com/prometheus-operator/kube-prometheus.git git checkout release-0.10 进入工作目录 cd kube…

二、[mysql]之Explain讲解与实战

目录 一、了解Explain1.Explain介绍 二、Explain相关字段1.partitions2.filtered3.SHOW WARNINGS命令 三、Explain比较重要字段1.id2.select_type3.table4.type5.possible_keys6.key7.key_len8.ref9.rows10.Extra 四、索引优化实战&#xff08;遵循原则&#xff09;1.全值匹配2…

python关闭指定进程以excel为例

先说下环境&#xff1a; Excel版本&#xff1a; Python2.7.13和Python3.10.4并存。 2、打开两个excel工作簿 看进程是这样的&#xff1a; 3、用python编程kill进程 # -*- coding: utf-8 -*- import os proc_nameEXCEL.EXE if __name__ __main__:os.system(taskkill /im {} /…

【vue2第十六章】VueRouter 声明式导航(跳转传参)、路由重定向、页面未找到的提示页面404、vue路由模式设置

声明式导航(跳转传参) 在一些特定的需求中&#xff0c;跳转路径时我们是需要携带参数跳转的&#xff0c;比如有一个搜索框&#xff0c;点击搜索的按钮需要跳转到另外一个页面组件&#xff0c;此时需要把用户输入的input框的值也携带到那页面进行发送请求&#xff0c;请求数据。…

python 随机生成emoji表情

问答板块觉得比较有意思的问题 当时搜了些网上的发现基本都不能用&#xff0c;不知道是版本的问题还是咋的就开始自己研究 python随机生成emoji 问题的产生解决官网文档数据类型实现思路实现前提&#xff1a;具体实现&#xff1a; 其他常见用法插入 Emoji 表情&#xff1a;解析…

【ES6】Class中this指向

先上代码&#xff1a; 正常运行的代码&#xff1a; class Logger{printName(name kexuexiong){this.print(hello ${name});}print(text){console.log(text);} }const logger new Logger(); logger.printName("kexueixong xiong");输出&#xff1a; 单独调用函数p…

搭建自己的OCR服务,第二步:PaddleOCR环境安装

PaddleOCR环境安装&#xff0c;遇到了很多问题&#xff0c;根据系统不同问题也不同&#xff0c;不要盲目看别人的教程&#xff0c;有的教程也过时了&#xff0c;根据实际情况自己调整。 我这边目前是使用windows 10系统CPU python 3.7 搭建。 熟悉OCR的人应该知道&#xff0…

合宙Air724UG LuatOS-Air LVGL API控件-标签 (Label)

标签 (Label) 标签是 LVGL 用来显示文字的控件。 示例代码 label lvgl.label_create(lvgl.scr_act(), nil) lvgl.label_set_recolor(label, true) lvgl.label_set_text(label, "#0000ff Re-color# #ff00ff words# #ff0000 of\n# align the lines …

B站:AB test [下]

Focus在&#xff1a;AB Test结束后&#xff0c;如何进行显著性检验&#xff1f;&#xff08;以判断改动是否有效果&#xff09; 引入&#xff1a;Z检验和T检验 而T检验适用于 n<30 的小样本 值得注意的是&#xff1a;统计上显著并不意味着现实中显著&#xff01; e.g. 加速…

尚硅谷大数据项目《在线教育之离线数仓》笔记008

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 P123 P124 P125 P126 P127 P128 P129 P123 Apache Superset是一个现代的数据探索和可视化平台。它功能强大且十分易用&#xff0c;可对接各种数据源&#xff0c;包括很多现代的大数…

医院空调冷热源设计方案VR元宇宙模拟演练的独特之处

作为一个备受关注的技术-元宇宙&#xff0c;毋庸置疑的是&#xff0c;因为建筑本身契合了时尚、前卫、高端、虚拟、科幻、泛在、协作、互通等元素特征&#xff0c;因此在建筑行业更需要元宇宙&#xff0c;以居民建筑环境冷热源设计来说&#xff0c;元宇宙会打破既定的现实阻碍和…

LVS NAT模式负载均衡群集部署

目录 1 群集(集群) cluster 1.1 群集的类型 2 LVS的工作模式及其工作过程 2.1 NAT模式&#xff08;VS-NAT&#xff09; 2.2 直接路由模式&#xff08;VS-DR&#xff09; 2.3 IP隧道模式&#xff08;VS-TUN&#xff09; 3 LVS-NAt 模式配置步骤 3.1 部署共享存储 3.2 配…

SSL证书验签时要带www吗?

单域名证书&#xff1a;顶级域名如www.abc.com或abc.com 不管你提交订单的时候填写的域名是带www或不带www的域名&#xff0c;签发的证书均支持www和不带www的域名 单域名证书&#xff1a;子域名如mail.abc.com&#xff0c;签发的证书仅支持mail.abc.com 通配符证书&#xff…

MySQL触发器使用指南大全

一、介绍 触发器是与表有关的数据库对象&#xff0c;指在insert/update/delete之前或之后&#xff0c;触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名OLD和NEW来引…