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

C++-list实现相关细节和问题

       前言:C++中的最后一个容器就是list,list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。下面我们就来模仿官方库中的list,自行实现一个list,并从中总结实现细节和经验。

目录

1.基本节点类和链表类的框架

数据节点类

list节点类

尾插函数

2.迭代器类的实现

迭代器的分类及实现

为什么不用析构函数和深拷贝?

为什么能使用前置++就不用后置++?

怪异的" -> "?地址解引用重载

如何在list类中使用迭代器

3.插入删除函数

4.const迭代器和拷贝赋值函数

const iterator和const_iterator?......有区别吗?

实现const_iterator

模板参数列表泛式编程

迭代器类终极版

拷贝构造和赋值函数

5.必须采用typename格式的场景

6.源码看这里

list.h

test.cpp


 

1.基本节点类和链表类的框架

数据节点类

        list的实现不同于其他的容器,比如vector等,list的底层是一个双向链表结构,每一个节点都由三部分组成,分别是数据,上一个节点和下一个节点,所以,对于list所存储的数据成员,我们需要单独开一个结构体来表示链表中的每一个数据成员,

template<class T>struct list_node //链表的数据成员结构{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T())//给一个匿名对象:_data(x), _next(nullptr), _prev(nullptr){}};

list节点类

接着就是list类的创建,我们知道,双线链表大致的结构如下所示,我们list的设计也基本遵循这个框架,

template<class T>class list {typedef list_node<T> Node;public:void empty_init()//空list初始化{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}size_t size(){return _size;}private:Node* _head;//虚拟的头结点,实际上第一个节点在该节点的下一个位置上,而尾结点end就是头结点的位置,这样,方便我们构成双向链表size_t _size;//链表长度,因为遍历一遍链表需要花费大量的时间,所以建议当做数据成员存储};

尾插函数

在上面功能的基础上,我们就可以实现简易的push_back函数了,这里就不再赘述,

void push_back(const T& x){//此时我们的最后一个元素是头结点的上一个元素,因为我们是一个双向循环链表,现在head和end都指向哨兵节点Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

2.迭代器类的实现

迭代器的分类及实现

list官方库中对迭代器的要求

       对于我们的list中的迭代器来说,我们常见的迭代器的使用需要满足,比如取数据,修改数据等,但是我们的list存储的实际上是一个个结构体,对结构体解引用编译器不认识,并且我们的链表存储并不是顺序表的顺序结构,而是随机存储结构,简单的对于迭代器地址增加并不能找到下一个数据,而且还涉及多种迭代器类型的使用,比如反向迭代器等,所以,list的迭代器需要具备取数据,++或者--自动到目标节点等等的功能,涉及重载函数等功能,因此,为了方便我们的使用,我们便让迭代器自成一个类,采用封装加重载的方式,这样我们就可以创建迭代器对象来更加方便地访问数据成员和其他操作。

为什么不用析构函数和深拷贝?

         迭代器本身作为一个链表的访问工具,但是如果写析构函数,在迭代器对象析构时,因为迭代器本身是指向链表节点的一个指针,释放指针必然会导致节点被影响,这是我们不允许的,所以不能写析构函数,采用默认的空操作的析构函数即可。

       而对于迭代器的深浅拷贝问题,迭代器本质上是节点的指针,但是并不是迭代器所指向的资源就归迭代器管理,我们只是把迭代器作为遍历和修改的工具,本质上就是让迭代器移动遍历,因此只需要浅拷贝链表节点即可。

为什么能使用前置++就不用后置++?

        我们看见上面的前置和后置++的区别在于一个返回本对象,另一个则返回本对象的拷贝,返回对象的拷贝的后置++在返回时返回的是临时对象tmp的拷贝,也就需要再次调用我们自定义的拷贝构造函数,在时间上不如前置++效率高。

怪异的" -> "?地址解引用重载

我们在上面的函数中有这样一个函数重载:

     当数据存在自定义对象时,比如我们的数据成员就是一个结构体,我们链表的节点保存的是结构体指针,那么我们如何取获得结构体中的数据成员的值呢,我们需要对迭代器进行解引用操作才能获取具体的元素的值,除了直接用(*it).xx来访问,我们还经常采用一种方式,就是it->xx的方式,上面这个函数就是我们需要采用该解引用方法时所需要重载的->运算符,重载完后即可正常使用地址解引用操作。

 

如何在list类中使用迭代器

         现在我们的迭代器自成一个类,但是我们需要在list类中使用它,我们就可以将迭代器定义在我们的类域内即可调用:

 

3.插入删除函数

      在官方库的inert和erase函数中,更加推荐使用迭代器作为返回值,在容器中,我们选择元素一般都会以迭代器的位置为基准,对于insert函数,因为不会涉及扩容问题,也就不存在迭代器失效问题,所以,我们可以返回插入元素位置的迭代器,方便对迭代器的再使用,同时对于erase函数,删除迭代器位置上的元素,节点删除必然会导致节点空间的释放,将必然导致迭代器失效,所以我们根据官方的文档中的解决方法,索性直接返回被删除元素的下一个位置的迭代器即可。

iterator insert(iterator pos,const T&x)//我们和官方文档的函数保持一致,返回新插入元素的迭代器(在pos位置之前插入){Node* newnode = new Node(x);//因为不存在扩容,所以不存在迭代器失效问题Node* prev = (pos._node)->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos._node;(pos._node)->_prev = newnode;++_size;return iterator(newnode);//类型转换}iterator erase(iterator pos)//返回删除元素的下一个位置{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;//释放掉后迭代器失效prev->_next = next;next->_prev = prev;--_size;return iterator(next);}

因为迭代器的使用,我们就可以顺势写出一下的常用函数接口:

~list()
{clear();//不释放头结点delete _head;_head = nullptr;
}void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}
void push_back(const T& x)
{insert(end(), x);//优化
}void push_front(const T& x)
{insert(begin(), x);
}void pop_front()
{erase(begin());
}void pop_back()
{erase(--end());
}

4.const迭代器和拷贝赋值函数

const iterator和const_iterator?......有区别吗?

      首先,我们要知道,const迭代器是修饰迭代器所指向的元素本身不能被改变而不是修饰迭代器本身的指向不能被改变const迭代器的类型相当于const T*而不是T*const,而const iterator相当于直接修饰了迭代器,相当于定死了迭代器就是指向某一个元素,这违背了我们使用迭代器作为遍历工具的原则,const迭代器产生就是为了遍历只读元素使用,而const_iterator就是这样一种特定的迭代器类型,这一点要做好区分。

实现const_iterator

     要实现const迭代器,我们就要知道,在原迭代器类中,哪些函数时能够影响到修改数据成员的,显然,只有对迭代器进行解引用才会影响数据修改,所以,我们只需要在原来的迭代器类的基础上,限定重载的解引用运算符即可,

当然,我们需要在list类中引入const迭代器类到类域中去:

模板参数列表泛式编程

     上面的const迭代器虽然实现了,但是我们要写两个迭代器类,而且两个迭代器类只有重载解引用操作时才会有不同的行为,而我们为了这个要多些一个类,实在是太麻烦了,我们就来看一看C++祖师爷是如何解决的吧,

        

       这也太香了,相当于模板参数列表里增加了两个模板参数,返回值根据参数来影响重载运算符的返回值类型,不愧是祖师爷,这也是泛式编程的思想,封装屏蔽了底层的实现细节,提供统一的访问方式。

不多说了,拿来吧你~~

迭代器类终极版

template<class T,class Ref,class Ptr>struct __list_iterator  //我们尽量与官方文档的保持一致{typedef list_node<T> Node;typedef __list_iterator<T,Ref,Ptr> self;//迭代器对象起别名Node* _node;__list_iterator(Node* node)//节点的指针可以隐式类型转换成迭代器:_node(node){}//对迭代器需要进行++和解引用操作,注意,迭代器的解引用应该是得到数据本身,所以我们的解引用也需要重载self operator++()//前置++{_node = _node->_next;return *this;}self& operator--()//前置--{_node = _node->_prev;return *this;}self& operator++(int)//后置++{self temp(*this);_node = _node->_next;return temp;}self& operator--(int)//后置++{self temp(*this);_node = _node->_prev;return temp;}//对于影响const迭代器调用的功能的函数传不同的返回值类型时使用Ref operator*(){return _node->_data;}Ptr operator->(){return &(_node->_data);}bool operator!=(const self& s)//迭代器比较结束条件判断{return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};

拷贝构造和赋值函数

       之所以把这一个知识放在迭代器实现完全之后再将,是因为我们在拷贝和赋值时经常需要传递const对象,而我们前面的迭代器是非const版本,无法对const对象进行解引用操作,也就导致了无法拷贝const对象,所以放在这里进行讲解,但是此时这两个函数用我们自己提供的接口完全可以实现,这里也不涉及深浅拷贝的问题,就直接给出了,

//拷贝构造list(const list<T>& lt){empty_init();for (auto e : lt)//迭代器重载后取得是数据元素,故可以使用push_back(e);}//赋值操作list& operator=(const list<T>& lt)//返回值也可以写list类型,类内均正确,但是为规范不建议{if (this != &lt){clear();//先清空原来的链表,但头结点还在for (auto e : lt)push_back(e);}return *this;}

5.必须采用typename格式的场景

基于以上的代码,我们来运行下面的测试样例:

template<class T>void print_list(const list<T>& lt){list<T>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);print_list(lt);list<string> lt2;lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");print_list(lt2);}

看上去没什么问题吧,我们也用了类模板,应该是能跑起来的,但是实际上,

这上面说,让我们在迭代器类型前面加一个typename,这是为什么呢?

     在编译阶段,编译器只能看到我们的list是list<T>类型的,模板还没有被实例化,导致编译器无法在对应的类中找到类域下的成员,向上面的const_iterator,编译器因为list<T>是未实例化的模板,所以也无法在确定的类域内找到const_iterator相应的实现和功能,所以编译器不认识这个变量是什么,也就导致了出错,前面加一个typename是告诉编译器,这里是一个类型,等list<T>实例化再去类里面去取。

void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);print_container(lt);list<string> lt2;lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");print_container(lt2);vector<string> v;v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");print_container(v);}

6.源码看这里

list.h

#pragma once
#include <vector>
namespace my_std
{template<class T>struct list_node //链表的数据成员结构{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T())//给一个匿名对象:_data(x), _next(nullptr), _prev(nullptr){} };template<class T,class Ref,class Ptr>struct __list_iterator  //我们尽量与官方文档的保持一致{typedef list_node<T> Node;typedef __list_iterator<T,Ref,Ptr> self;//迭代器对象起别名Node* _node;__list_iterator(Node* node)//节点的指针可以隐式类型转换成迭代器:_node(node){}//对迭代器需要进行++和解引用操作,注意,迭代器的解引用应该是得到数据本身,所以我们的解引用也需要重载self operator++()//前置++{_node = _node->_next;return *this;}self& operator--()//前置--{_node = _node->_prev;return *this;}self& operator++(int)//后置++{self temp(*this);_node = _node->_next;return temp;}self& operator--(int)//后置++{self temp(*this);_node = _node->_prev;return temp;}//对于影响const迭代器调用的功能的函数传不同的返回值类型时使用Ref operator*(){return _node->_data;}Ptr operator->(){return &(_node->_data);}bool operator!=(const self& s)//迭代器比较结束条件判断{return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};template<class T>class list {public:typedef list_node<T> Node;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 const_iterator(_head->_next);//含隐式类型转换,自己写转不转换都行}const_iterator end()const{return _head;}void empty_init()//空list初始化{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}//拷贝构造list(const list<T>& lt){empty_init();for (auto e : lt)//迭代器取得是数据元素,故可以使用push_back(e);}//赋值操作list& operator=(const list<T>& lt)//返回值也可以写list类型,类内均正确,但是为规范不建议{if (this != &lt){clear();//先清空原来的链表,但头结点还在for (auto e : lt)push_back(e);}return *this;}~list(){clear();//不释放头结点delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){//此时我们的最后一个元素是头结点的上一个元素,因为我们是一个双向循环链表,现在head和end都指向哨兵节点/*Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;*/insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos,const T&x)//我们和官方文档的函数保持一致,返回新插入元素的迭代器(在pos位置之前插入){Node* newnode = new Node(x);//因为不存在扩容,所以不存在迭代器失效问题Node* prev = (pos._node)->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos._node;(pos._node)->_prev = newnode;++_size;return iterator(newnode);//类型转换}iterator erase(iterator pos)//返回删除元素的下一个位置{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;//释放掉后迭代器失效prev->_next = next;next->_prev = prev;--_size;return iterator(next);}size_t size(){return _size;}private:Node* _head;//虚拟的头结点,实际上第一个节点在该节点的下一个位置上,而尾结点end就是头结点的位置,这样,方便我们构成双向循环链表size_t _size;//链表长度,因为遍历一遍链表需要花费大量的时间,所以建议当做数据成员存储};void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);// 封装屏蔽底层差异和实现细节// 提供统一的访问修改遍历方式list<int>::iterator it = lt.begin();//迭代器需要单独编写迭代器类,以供应需求,说清楚为什么需要编写(迭代器的种类),重载哪些运算符等while (it != lt.end()){*it += 20;cout << *it << " ";it++;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;}void test_list2(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int> lt1(lt);for (auto e : lt){cout << e << " ";}cout << endl;for (auto e : lt1){cout << e << " ";}cout << endl;list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);lt2.push_back(40);lt2.push_back(50);lt1 = lt2;for (auto e : lt1){cout << e << " ";}cout << endl;for (auto e : lt2){cout << e << " ";}cout << endl;}struct AA{AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test_list3(){list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));list<AA>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1<<" "<<(*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;//本来应该是it->->_a1,编译器特殊处理,省略了一个->cout << it.operator->()->_a1 << " " << it.operator->()->_a1 << endl;//显式调用运算符重载函数++it;}cout << endl;}//typename场景template<class T>void print_list(const list<T>& lt){typename list<T>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;}//再抽象,我们可以将类型再抽象为容器,这样就可以打印list之外的容器的元素template<typename Container>void print_container(const Container& lt){typename Container::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;}void test_list4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);print_container(lt);list<string> lt2;lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");lt2.push_back("1111111111111");print_container(lt2);vector<string> v;v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");v.push_back("222222222222");print_container(v);}
}

test.cpp

#include<iostream>
using namespace std;
#include"list.h"int main()
{my_std::test_list4();return 0;
}

        当你越来越专注,越来越自律,顺其自然得社交,随时随地地启程,开始爱惜自己,不在意他人的议论揣测,拿得起放得下,你就成为了很棒的自己。 

相关文章:

C++-list实现相关细节和问题

前言&#xff1a;C中的最后一个容器就是list&#xff0c;list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指…...

hadoop学习:mapreduce的wordcount时候,继承mapper没有对应的mapreduce的包

踩坑描述&#xff1a;在学习 hadoop 的时候使用hadoop 下的 mapreduce&#xff0c;却发现没有 mapreduce。 第一反应就是去看看 maven 的路径对不对 settings——》搜索框搜索 maven 检查一下 Maven 路径对不对 OK 这里是对的 那么是不是依赖下载失败导致 mapreduce 没下下…...

windows10上搭建caffe以及踩到的坑

对动作捕捉的几篇论文感兴趣&#xff0c;想复现一下&#xff0c;需要caffe环境就折腾了下&#xff01;转模型需要python 2.7环境&#xff0c;我顺便也弄了&#xff01;&#xff01;&#xff01; 1. 环境 Windows10 RTX2080TI 11G Anaconda Python2.7 visual studio 2013 cuda…...

大数据Flink(七十):SQL 动态表 连续查询

文章目录 SQL 动态表 & 连续查询 一、​​​​​​​SQL 应用于流处理的思路...

「MySQL-04」Linux环境下使用C/C++连接并操纵MySQL

目录 一、准备mysql库&#xff1a;Connector/C 1. 查看是否有mysql相关的库和头文件 2. 安装devel(开发库) 3.到官网下载开发包&#xff0c;并上传到Linux 3.0 须知 3.1 到官网下载开发包 3.2 上传安装包至Linux 二、mysql库&#xff1a;Connector/C 的使用 1. 创建并初始化mys…...

【力扣】两数相除(c/c++)

目录 题目 注意&#xff1a; 示例 1: 示例 2: 提示&#xff1a; 题目解析 题目思路 代码思路 数据处理 注意 减法函数 第一次使用的函数 问题 第二次改良后的代码 处理i的值并且返回 总代码 力扣的代码 注意 题目 给你两个整数&#xff0c;被除数 dividend 和…...

《Kubernetes部署篇:Ubuntu20.04基于二进制安装安装kubeadm、kubelet和kubectl》

一、背景 由于客户网络处于专网环境下&#xff0c; 使用kubeadm工具安装K8S集群&#xff0c;由于无法连通互联网&#xff0c;所有无法使用apt工具安装kubeadm、kubelet、kubectl&#xff0c;当然你也可以使用apt-get工具在一台能够连通互联网环境的服务器上下载kubeadm、kubele…...

go学习part21 Redis

300_尚硅谷_Redis的基本介绍和原理示意_哔哩哔哩_bilibili Redis 命令 | 菜鸟教程 (runoob.com) 1.基本介绍 2.基本操作 Redis的基本使用: 说明:Redis安装好后&#xff0c;默认有16个数据库&#xff0c;初始默认使用0号库,编号是0...15 1.添加key-val [set] 2.查看当前redi…...

时序预测 | MATLAB实现基于PSO-BiGRU、BiGRU时间序列预测对比

时序预测 | MATLAB实现基于PSO-BiGRU、BiGRU时间序列预测对比 目录 时序预测 | MATLAB实现基于PSO-BiGRU、BiGRU时间序列预测对比效果一览基本描述程序设计参考资料 效果一览 基本描述 1.时序预测 | MATLAB实现基于PSO-BiGRU、BiGRU时间序列预测&#xff1b; 2.单变量时间序列数…...

Unity3D下如何采集camera场景数据并推送RTMP服务?

Unity3D使用场景 Unity3D是非常流行的游戏开发引擎&#xff0c;可以创建各种类型的3D和2D游戏或其他互动应用程序。常见使用场景如下&#xff1a; 游戏开发&#xff1a;Unity3D是一个广泛用于游戏开发的环境&#xff0c;适用于创建各种类型的游戏&#xff0c;包括动作游戏、角…...

黑客可利用 Windows 容器隔离框架绕过端点安全系统

新的研究结果表明&#xff0c;攻击者可以利用一种隐匿的恶意软件检测规避技术&#xff0c;并通过操纵 Windows 容器隔离框架来绕过端点安全的解决方案。 Deep Instinct安全研究员丹尼尔-阿维诺姆&#xff08;Daniel Avinoam&#xff09;在本月初举行的DEF CON安全大会上公布了…...

STM32注入通道

什么是注入通道? 注入通道是ADC的一种采样方式,主要用于在规则通道转换期间并行处理快速变化信号的采样。注入通道的转换可以在规则通道转换时强行插入,相当于一个“中断通道”。当有注入通道需要转换时,规则通道的转换会停止,优先执行注入通道的转换,当注入通道的转换执…...

WebVR — 网络虚拟现实

推荐&#xff1a;使用 NSDT编辑器 快速搭建3D应用场景 虚拟现实设备 随着Oculus Rift和许多其他生产设备即将上市&#xff0c;未来看起来很光明——我们已经有足够的技术来使VR体验“足够好”&#xff0c;可以玩游戏。有许多设备可供选择&#xff1a;像Oculus Rift或HTC Vive这…...

ASP.NET Core 的 Routing

ASP.NET Core 的 Routing ASP.NET Core 的 controllers 使用Routing 中间件匹配客户端的 url 请求&#xff0c;然后映射到对应的 controller 的处理方法&#xff08;Action&#xff09;上。 Actions 可以是 常规路由 或 属性路由 的映射。 MVC App一般使用常规路由。 REST API…...

IBM Spectrum LSF Explorer 为要求苛刻的分布式和任务关键型高性能技术计算环境提供强大的工作负载管理

IBM Spectrum LSF Explorer 适用于 IBM Spectrum LSF 集群的强大、轻量级报告解决方案 亮点 ● 允许不同的业务和技术用户使用单一解决方案快速创建和查看报表和仪表板 ● 利用可扩展的库提供预构建的报告 ● 自定义并生成性能、工作负载和资源使用情况的报…...

RHCE——十一、NFS服务器

NFS服务器 一、简介1、NFS背景介绍2、生产应用场景 二、NFS工作原理1、示例图2、流程 三、NFS的使用1、安装2、配置文件3、主配置文件分析3.1 实验1 4、NFS账户映射4.1 实验24.2 实验3 四、autofs自动挂载服务1、产生原因2、安装3、配置文件分析4、实验45、实验5 一、简介 1、…...

Python编程练习与解答 练习100:随机密码

编写一个生成最忌密码的函数&#xff0c;密码的长度应该在7-10个字符之间。每个字符应该从ASCII表的第33位到126位中随机选择。函数不接受任何参数&#xff0c;返回随机生成的密码作为位移结果。在文件的main程序中显示随机生成的密码。main程序只在解答没有被导入另一个文件时…...

华为云云服务器评测 | 从零开始:云耀云服务器L实例的全面使用解析指南

文章目录 一、前言二、云耀云服务器L实例要点介绍2.1 什么是云耀云服务器L实例2.1.1 浅析云耀云服务器L实例 2.2 云耀云服务器L实例的产品定位2.3 云耀云服务器L实例优势2.4 云耀云服务器L实例支持的镜像与应用场景2.5 云耀云服务器L实例与弹性云服务器&#xff08;ECS&#xf…...

欧科云链研究院探析Facebook稳定币发行经历会不会在PayPal重演

引言 作者最近的报告-探析PayPal发行稳定币是否会重蹈Facebook覆辙-近期被英国的金融时报&#xff08;中文版&#xff09;刊登。由于该报告在欧科云链研究院内部反响较好&#xff0c;下面就带大家简单的剖析这篇报告的主要内容。 *这篇文章主要由对比分析&#xff08;已删减&a…...

docker 容器pip、git安装异常;容器内web对外端口ping不通

1、docker 容器pip、git安装异常 错误信息&#xff1a; git clone https://github.com/vllm-project/vllm.git Cloning into ‘vllm’… fatal: unable to access ‘https://github.com/vllm-project/vllm.git/’: Failed to connect to 127.0.0.1 port 10808: Connection ref…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...