C++-----list

本期我们来讲解list,有了string和vector的基础,我们学习起来会快很多

目录

list介绍

​编辑

list常用接口 

 insert

erase

reverse

sort 

merge

 unique

remove

splice

 模拟实现

基础框架

构造函数

 push_back

迭代器

 常见问题

const迭代器

insert

erase

push和pop

size

析构和clear 

拷贝构造

赋值

全部代码


本期内容需要比较扎实的基础

list介绍

 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

2. list 的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比 (array vector deque) list 通常在任意位置进行插入、移除元素的执行效率更好。
5. 与其他序列式容器相比, list forward_list 最大的缺陷是不支持任意位置的随机访问,比如:要访问 list的第6 个元素,必须从已知的位置 ( 比如头部或者尾部 ) 迭代到该位置,在这段位置上迭代需要线性的时间开销;list 还需要一些额外的空间,以保存每个节点的相关联信息 ( 对于存储类型较小元素的大 list 来说这可能是一个重要的因素)

 简单来说,list就是带头双向循环列表

我们先简单看看如何使用 

list常用接口 

list的迭代器啦,构造函数,析构函数这些跟我们前面学的都是一样的,大家稍微看看文档即可,这里就不细讲了

不过list不一样的是它有头插头删,尾插尾删,这些和我们以前用C语言实现的是一样的

 insert

 我们从vector开始,insert里的参数都不是pos,而是迭代器了

我们之前使用vector时是可以使用迭代器+一个数的,而list是不行的,因为vector是数组,是连续的空间,而list是链表 ,是一个个节点

 我们想在5的位置插入,是需要手动让迭代器走的,然后再插入

我们上面插入了1,2,3,4,5,如果我们想在数字3前面插入数据,是需要使用find的,但是我们看文档,list是没有提供find的,我们在vector里说过,find属于算法,通过迭代器来和算法联系起来,我们通过统一的方法,而不用关注容器底层是如何实现的

 我们来看find的查找,它的循环条件是first!=last,而不是小于,我们用while来进行遍历,也使用的是begin!=end,而不是小于,因为后面节点的地址不一定比前面小

通过迭代器,不止是vector,list,即使是树也是可以查找的,因为都是迭代器,区间范围是左闭右开

比如我们在3前面插入30

并且由于是带头双向循环的原因,insert是不存在迭代器失效问题的

erase

虽然insert没有迭代器失效问题,但是erase是有的

 

节点都不存在了

 insert以后,it不失效,erase以后,it会失效

如果我们要删掉所有的偶数呢?

是需要使用返回值的,和vector是一样的

 返回的是刚刚被删除的元素的后一个

reverse

 reverse是链表的逆置,其实有点多余了,因为算法库里也有

意义其实不大

sort 

sort在库里面也有,那是否也是多余的呢?

这里编译报错了

 sort编译报错了,原因是sort底层是快排,快排是需要三叔数取中的,会取到这个数的位置,链表是不适应这个场景的

  我们仔细看算法库里sort,find,reverse这些,都是模板,我们仔细看参数名字,sort的是random,reverse的是bidirection,一个是随机的意思,一个是双向的意思,这些都是暗示,这些名字都不是随便起的

这里就和迭代器有关了,迭代器分为单向迭代器,双向迭代器和随机迭代器,单向迭代器只能++,双向可以++也可以- -,而随机不仅可以++和- -,还可以+和-

单链表的迭代器就是单向迭代器,双向链表,也就是list就是双向迭代器,还有map和set也是,vector和string是随机迭代器

find的迭代器是input,是所有迭代器都可以使用,这里涉及到继承,我们后面会讲 

那我们该如何知道一个容器的迭代器属于什么呢?

其实文档里都有说明 ,比如list的迭代器是双向的

但迭代器的使用不是定死的,比如string,vector都可以使用reverse,因为随机迭代器可以看作特殊的双向迭代器,是可以兼容的,比如双向可以使用单向的

我们再回过头来看list的sort还有意义吗?有一点,但不多

如果要排序的话,我们应该使用vector,而不是list,在500w的数据量时,vector能比list快10倍

list的排序底层使用的是归并排序,所以list的sort的意义就是方便

我们要排序的话

  可以这样做,把list的数据拷贝到vector,然后再拷贝回来 

所以各位以后在排序时,尤其是数据量大时,不要用list的sort

merge

merge是两个链表的归并

不过归并前先排序一下

 unique

unique去重的意思,不过去重前也是需要先排序的,不然效率太低了

remove

 remove就是find加erase

 

找不到的话就什么也不做,找到就删除 

splice

 splice是转移,可以将a链表的节点拿下来转移到b链表

我们看第一个接口,是把x链表的所有值转移到当前链表(position)之前,第二个是转移x链表的i,第三个是转移一个区间

这里是调用第一个接口 ,是全部转移,此时mylist2是空的

这里调用第二个接口,我们把第二个链表的第二个数转移到第一个链表 

这是第三个接口 ,我们把第二个链表的第二个位置开始全部转移

还可以自己转移自己

我们把第二个位置转移到第一个位置的前面

 

还有不要有重叠,像这样就死循环了

 模拟实现

下面我们来进行模拟实现

首先我们先看看源代码

我们看到有一个void*类型

我们还找到了链表

还找到了成员变量

它的本质是这样的

我们还看到了无参的构造函数 ,初始化了一个节点

getnode是哨兵位节点

putnode下面我们发现了熟人,construct 

 这些我们了解一下即可

我们还可以找到pushback ,我们发现它调用了insert

我们就不再深入,各位感兴趣的话可以自己再看看,下面我们进入正题

基础框架

 我们使用struct定义节点,使用class也可以,不过需要使用public,节点不是公有的使用起来会非常麻烦

补全一下,最后写成这样,看框住的地方,有人可能直接在private里写list_node*  _head,这样是错误的,因为 忘记了<T>

构造函数

        list(){_head = new Node;_head->_prev = _head;_head->_next = _head;}

 除了list里要写构造函数,节点里也要写一个,因为我们在插入节点时是需要创建的,而创建时调用构造函数即可

 push_back

        void push_back(const T& x){Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

其实写起来和我们以前用C实现的是一样的

迭代器

首先,大家想一想我们的迭代器用Node*可以吗?

大家想一想,数组是连续的,我们解引用后就可以直接用,++就可以到下一个位置,但是链表不行,它不是连续的,即使当前是连续的,我们插入一个值,也会变成不连续的 

 我们当时日期对象++是如何到下一天的?是调用了一个函数,是需要运算符重载

 我们看库里面,是这样实现的 ,我们要++的话,就是让node=node->next

深入看库对于现在的我们来说还是有点难度的,我们就不再往下看

所以我们最后迭代器是这样一个东西 

现在需要写begin和end

我们遍历是这样写的

begin是第一个数据,end是最后一个数据的下一个位置 

 我们看库里面,库里面的node是我们的head,end返回了node,begin返回了node的next,和我们是一样的

迭代器是自定义类型,是节点指针,但因为底层原因,不是连续的,我们不能用原生的,不像之前的vector等等,C++可以用一个类来封装内置类型,然后重载运算符,比如++就是调operator++,就变成我们来控制了,就像C里面指针是不符合我们行为的,我们可以把它写的和指针一样,但是底层不一样

就像这样,再想想我们要写list的遍历,也是这样写,但是*it看起来一样,实际不一样,一个数组,一个链表,甚至一棵树,遍历都可以这样写,封装和运算符重载的力量是非常强大的

我们从应用的角度来实现,即从遍历这里开始写怎么写迭代器

我们补全一下__list_iterator,再看 begin,这里为什么可以这样返回呢?因为单参数的构造函数支持隐式类型转换,就像const char*可以转换为string一样

还可以这样写,本质是一样的

 我们再想想解引用该怎么办

转换为调用operator*,返回节点里的值,因为出了作用域节点还在,val也还在,所以我们返回引用 

同样的,我们还要写++,我们让node=node->next即可

 

我们还要写不等于

此时我们进行测试发现报错了,报错的是!=

原因是在我们的循环条件里,去调用operator!= ,然后我们的operator!=的参数我们给的是引用,调用了end,end返回的是iterator,是传值返回,传值返回的是head的拷贝,是临时对象,具有常性,所以我们在!=要加const

也就是这样

此时就没有问题了 

我们上面实现了++,我们++有前置++,还有后置++,下面是后置++的实现

 同样的,我们实现了不等于,也要实现等于

 常见问题

下面再解释一下第一次学习的同学一些常见问题

为什么两个typedef不在一块,iterator是对外用的,所以放在public里,而Node是对内用的,我们不希望别人访问我们的节点,别人使用迭代器就可以了,所以不在一块 

还有为什么我们起名是list_node,而不是node,迭代器也是,因为除了list,还有vector,树等等各种结构,他们都在std这个命名空间里面

就像我写的这个namespace bai,官方的std,它所有的都在这里面,所以不这样取名会存在命名冲突问题 

还有一个疑惑,迭代器的本质是通过自定义类型封装,改变了原生指针的行为,达到我们的目的

再看一个问题,这里调用begin,begin的值是如何给it的? 是拷贝构造,这里不是赋值

我们现在的迭代器没有写拷贝构造,默认生成的拷贝构造对内置类型完成值拷贝,也就是浅拷贝,但是没有问题,我们要的就是浅拷贝

 begin返回这个位置的迭代器,给it,我们就期望it里面的节点指针也是指向这个位置

但是两个对象指向同一个节点,为什么这里没有崩溃?

崩溃的核心原因在于析构函数多次释放,但是迭代器对象我们没写析构函数,原因是节点不是属于迭代器的,不需要迭代器来进行释放,我们只是借助迭代器来访问容器,不论数组,链表,树,我们都可以用同样的方式来访问容器,而不是管理容器,节点是由链表释放的

const迭代器

大家先想想这样设计const迭代器对吗?

 迭代器模拟的是指针,指针有两种const指针

 const迭代器模拟的是第一种指针的行为,第二个指针是不能++的,const在*后面,修饰的是指针本身,而第一个是修饰指向的内容不能修改,const迭代器是期望指向的内容不能修改

此时我们再看我们上面设计的,是不行的,我们模拟的是上面的ptr2,这样写出来的是const迭代器本身不能修改,我们就不能遍历,不能++了,因为++是非const,const是不能调用非const的,同样我们也不能把++变为const,因为++里要改变成员变量

我们怎么写才能控制指向的内容不能修改?

我们控制解引用即可 ,这样返回的就是const引用

此时就会有人把上面迭代器整个拷贝一份,然后改改名什么的,那样设计太冗余了

我们再看库里面,它加了两个模板参数 

 我们先在自己的迭代器这里加一个Ref,然后修改operator*的返回值类型

接着我们在list里就可以typedef const迭代器了 

其实这样写也相当于我们写了两个类,大家想一想,vector<int>和vector<double>是两个类,是两个相似的类,有编译器通过模板生成

而我们这样写,通过模板参数,给T引用的时候,operator*返回的就是T引用,给const T引用时返回的就是const T引用,但是他们两个是两个类,给不同的模板参数就是不同的类

这样写的话,我们还要修改一些东西

我们要在迭代器类里写这样一个,我们习惯叫做self,就是自己的意思 

接着我们把这些修改为self即可

还有别忘记写const的begin和end

 我们继续看源码发现,除了重载operator*之外,还重装了这样一个,并且库里面是三个模板参数,我们现在只有两个 

我们知道,指针除了*解引用,还有->解引用,*是取指针指向的数据,指向的对象,那->呢?

如果是一个结构体的指针,就需要->,迭代器是模拟指针的行为,什么时候模拟结构体指针呢?

 operator->默认返回的是T*

 我们先看这样一段代码,我们下面*it解引用是什么? 这段代码为什么不能运行?

因为*it是取里面的数据T,T是A,而A是自定义类型,这里是需要重载流插入的,这是第一种方法,如果我们不想重载,A里的成员变量a1,a2都不是私有的

 我们是可以这样写的 

此时迭代器是模拟的指向结构体的指针,如果这里类型是int,我们直接解引用,如果是自定义类型,是需要用箭头的

是可以这样写的 

但是这里有个非常诡异的问题

 首先,我们把operator->屏蔽掉的话这里是会报错的 

 但是,这个箭头调用是非常奇怪的,operator*是没问题的

如果是*it,这里就是it.operator*() ,然后operator*返回A&,A.a1,A.a2是没有问题的

而箭头是it.operator->(),返回的是A*,A*怎么去访问呢?

所以这里严格来说严格是it->->_a1,才是符合语法的,是两个箭头,第一个箭头的运算符重载,调用operator->返回A*,A*再加一个箭头才能访问

因为运算符重载要求可读性,所以编译器在这里是特殊处理的,省略了一个箭头

如果是const迭代器,operator->应该返回const T*

 所以我们需要加第三个模板参数Ptr

然后用Ptr代替T* 

普通迭代器传T*,const传const T*

 我们上面实现的思路大家也发现了,我们要从核心的东西开始看,如果一上来我们就看到迭代器有三个参数,人都是懵的,是看不明白的,所以看不懂的东西我们可以先暂时放下,先看后面的,看它的核心,看它的功能,慢慢就可以知道了,我们看不懂的原因是当前站的高度太低了,很多东西是需要看好几遍的

另外这些不修改的我们最好再加上const 

        self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}

再补充一下-- 

insert

        void insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;}

insert非常简单,这里是可以体先出为什么我们节点使用的是结构体,而不是类,如果是类的话,会变得麻烦一点,我们是和库保持一致的,库里面也使用的是结构体,而且我们不用担心有人会写it._node这种代码,因为官方只规定了迭代器可以++这些,而没有规定底层,我们不确定这里是_node,还是Node,或者是_Node,即使我们去查了源码,也只是针对当前平台,换一个平台可能就不同了

库里面和我们是差不多的 

erase

        void erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;}

erase是需要先检查一下的,end是哨兵位,不能删除,另外,我们可以发现,这里是存在迭代器失效问题的,所以我们需要返回下一个位置,另外我们上面还看到了insert其实也有返回值,我们一块补充一下

        iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return next;}

push和pop

        void push_back(const T& x){/*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_back(){erase(--end());}void pop_front(){erase(begin());}

这些都没什么说的,有了insert和insert,我们都是可以复用的

size

        size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;}

我们可以这样遍历一遍,如果感觉这样不好,也可以加一个成员变量_size

 这样写我们要修改一下insert和erase

++一下即可,其他地方不用变 ,同理,erase里--一下即可

还有初始化也要修改一下

析构和clear 

        ~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}

clear和析构是不一样的,clear是清理数据,清理还要释放空间,不过哨兵位是不清的,所以我们需要接受一下,it刚好接受下一个位置,最后处理一下size即可,析构直接复用即可

  我们简单测试一下,没有问题 

拷贝构造

        list(const list<T>& lt)//拷贝构造{_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;for (auto& e : lt){push_back(e);}}

我们把数据一个一个的放进去即可

这里是有点冗余,我们提取一下公共部分,库里面也是这样写的 

赋值

        void swap(list<T>& lt){std::swap(_head,lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt)//赋值{swap(lt);return *this;}

和vector一样,我们直接用现代写法

测试一下也没有问题 

 

仔细看我们和库里面的区别,库里面最开始是list&,我们是list<T>&

 我们写的是类型,它写的是类名

 对于拷贝构造和赋值,写类名是允许的,不过这样写有点降低可读性,我们这里不推荐和库一样

类模板在类里面使用,既可以写类名,也可以写类型,虽然语法上允许,不过我们最好保持统一写类型

全部代码

#include<iostream>
#include<list>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace bai
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}};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){}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& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._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;//typedef const __list_iterator<T> const_iterator;//错误的iterator begin(){//return _head->_next;return iterator(_head->_next);}iterator end(){return _head;//return iterator(_head);}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{return _head;//return const_iterator(_head);}void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();}//list(const list& lt)//拷贝构造list(const list<T>& lt)//拷贝构造{empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head,lt._head);std::swap(_size, lt._size);}//list& operator=(list lt)//赋值list<T>& operator=(list<T> lt)//赋值{swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}void push_back(const T& x){/*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_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}size_t size(){/*size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;*/return _size;}private:Node* _head;size_t _size;};
}

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

MySQL存储过程

文章目录 MySQL存储过程一.MySQL存储过程1.概述2.简介3.存储过程中的控制结构及应用场景4.存储过程的优点5.存储过程语法6.不带参数创建&#xff08;示例&#xff09;6.1 创建存储过程6.2 调用存储过程6.3 查看存储过程6.4 存储过程的参数 7.实例8.修改存储过程9.删除存储过程 …

4 Linux基础篇-目录结构

4 Linux基础篇-目录结构 文章目录 4 Linux基础篇-目录结构4.1 Linux目录结构4.2 具体的目录结构 学习视频来自于B站【小白入门 通俗易懂】2021韩顺平 一周学会Linux。可能会用到的资料有如下所示&#xff0c;下载链接见文末&#xff1a; 《鸟哥的Linux私房菜 基础学习篇 第四版…

springboot 之以enable开头的注解

Spring​ 有很多 Enable 开头的注解&#xff0c;平时在使用的时候也没有注意过为什么会有这些注解 Enable 注解 首先我们先看一下有哪些常用的 Enable 开头的注解&#xff0c;以及都是干什么用的。 EnableRetry​&#xff1a;开启Spring 的重试功能&#xff1b; EnableSch…

web攻击面试|网络渗透面试(一)

Web攻击面试大纲 常见Web攻击类型 1.1 SQL注入攻击 1.2 XSS攻击 1.3 CSRF攻击 1.4 命令注入攻击SQL注入攻击 2.1 基本概念 2.2 攻击原理 2.3 防御措施XSS攻击 3.1 基本概念 3.2 攻击原理 3.3 防御措施CSRF攻击 4.1 基本概念 4.2 攻击原理 4.3 防御措施命令注入攻击 5.1 基本概…

手机python怎么用海龟画图,python怎么在手机上编程

大家好&#xff0c;给大家分享一下手机python怎么用海龟画图&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 1、如何python手机版创造Al&#xff1f; 如果您想在手机上使用Python来创建AI&#xff08;人工智能&#xff09;程序&#xff0…

Golang并发控制

开发 go 程序的时候&#xff0c;时常需要使用 goroutine 并发处理任务&#xff0c;有时候这些 goroutine 是相互独立的&#xff0c;需要保证并发的数据安全性&#xff0c;也有的时候&#xff0c;goroutine 之间要进行同步与通信&#xff0c;主 goroutine 需要控制它所属的子gor…

nginx怎么做负载均衡

Nginx怎么做负载均衡 Nginx 是一个高性能的开源反向代理服务器&#xff0c;可以用于实现负载均衡。负载均衡指的是将用户请求平均分配给多个服务器&#xff0c;以提高整体系统性能和可靠性。下面是一个详细介绍如何使用 Nginx 实现负载均衡的步骤&#xff1a; 步骤 1&#xf…

Redis7.0版本集群

环境准备 centos 7.5redis-7.0.12.tar.gz 操作步骤 1、采用克隆模式创建三台虚拟机&#xff08;redis-cluster01、redis-cluster02、redis-cluster03&#xff09; 2、上传 redis-7.0.12.tar.gz 文件到 /home 目录下 3、解压文件 4、进入 /home/redis-7.0.12 目录下执行命令 m…

K8S集群创建和管理,以及常用命令

目录 1. 安装 K8s 集群2. 创建 K8s 集群3. 管理 K8s 集群4. 维护 K8s 集群 K8s 集群 (Kubernetes Cluster) 是一个由多个节点组成的容器编排平台&#xff0c;它提供了一种简单、可靠、可扩展的方式来部署、管理和监控容器化应用程序。K8s 集群通常由一个或多个 Master 节点和一…

VB+ACCESS教学管理系统设计与实现

【内容摘要】 本学籍管理系统是采用VISAUL BASIC6.0开发的一个数据库管理系统。本设计说明书主要讲述了VISAUL BASIC6.0的基本功能及设计方法。紧接着以本系统为例,逐一介绍开发本系统系统的步骤:系统分析、系统设计、系统实现、系统维护。在系统分析中先后用数据流图、数据…

pg_archivecleanup清理wal日志

一、 注意事项 pg_archivecleanup代码中仅进行了wal日志文件名的对比&#xff0c;没有实现对WAL日志名及对应生成时间的判断。在WAL日志未被重命名时&#xff0c;时间与日志名顺序名一致&#xff0c;没有问题。一旦WAL日志被重命名&#xff0c;pg_archivecleanup清理就可能清理…

Hadoop 之 Hbase 配置与使用(四)

Hadoop 之 Hbase 配置与使用 一.Hbase 下载1.Hbase 下载 二.Hbase 配置1.单机部署2.伪集群部署&#xff08;基于单机配置&#xff09;3.集群部署1.启动 hadoop 集群2.启动 zookeeper 集群3.启动 hbase 集群4.集群启停脚本 三.测试1.Pom 配置2.Yml 配置3.Hbase 配置类4.Hbase 连…

C#中try catch finally 用法详解

本文详解try catch finally 用法 目录 定义 这些代码块的用法如下:

大数据课程D1——hadoop的初识

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解大数据的概念&#xff1b; ⚪ 了解大数据的部门结构&#xff1b; ⚪ 了解hadoop的定义&#xff1b; ⚪ 了解hadoop的发展史&#xff1b; 一、大数据简介 1. 概述…

STM32 Flash学习(二)

STM32F1的官方固件库操作FLASH的几个常用函数。这些函数和定义分布在源文件stm32f1xx_hal_flash.c/stm32f1xx_hal_flash_ex.c以及头文件stm32f1xx_hal_flash.h/stm32f1xx_hal_flash_ex.h中。 锁定解函数 对FLASH进行写操作前必须先解锁&#xff0c;解锁操作&#xff1a;在FLA…

GB/T 25000.51解读——软件产品的兼容性怎么测?

GB/T 25000.51-2016《软件产品质量要求和测试细则》是申请软件检测CNAS认可一定会用到的一部国家标准。在前面的文章中&#xff0c;我们为大家整体介绍了GB/T 25000.51-2016《软件产品质量要求和测试细则》国家标准的结构和所涵盖的内容以及对软件产品的八大质量特性中的功能性…

GPU压力测试篇- TensorFlow

简介 该文档介绍使用Tensorflow框架&#xff0c;测试 NVIDIA 驱动的常见python 代码。 环境信息 编号 软件 软件版本 备注 01 驱动 470.57.02 02 cuda 版本 11.2 03 cudnn 版本 8.1.1.33 04 tensorflow 2.6 功能测试代码&#xff1a; import tensor…

基于Matlab实现30种电力电子仿真模型(附上完整源码)

本文介绍了如何使用Matlab来实现电力电子仿真模型&#xff0c;并提供了一个简单的代码示例&#xff0c;展示了如何建立一个逆变器的仿真模型并进行仿真分析。 文章目录 1. 引言2. Matlab实现电力电子仿真模型的基本步骤3. 逆变器仿真模型的示例代码4. 结果分析5. 结论6. 30个完…

如何备份与恢复MySQL数据库数据

目录 数据备份的重要性 造成数据丢失的原因 备份的主要目的 日志 数据库备份类型 逻辑备份 完全备份 差异备份 增份&#xff08;增量备份&#xff09; 备份方式比较 三、常见的备份方法 物理冷备 专用备份工具mysqldump 或mysqlhotcopy 启用二进制日志进行增量备份…

第5讲-搭建kubernetes集群

一、minikube ​ minikube 是一个工具&#xff0c; 能让你在本地运行 Kubernetes。 minikube 在你的个人计算机&#xff08;包括 Windows、macOS 和 Linux PC&#xff09;上运行一个一体化&#xff08;all-in-one&#xff09;或多节点的本地 Kubernetes 集群&#xff0c;以便你…