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

西安保障性住房建设投资中心网站/推广公司

西安保障性住房建设投资中心网站,推广公司,大连旅游网站建设大概多钱,福建网站建设哪家专业目录 1、list介绍 所要实现类及其成员函数接口总览 2、结点类的模拟实现 基本框架 构造函数 3、迭代器类的模拟实现 迭代器类存在的意义 3.1、正向迭代器 基本框架 默认成员函数 构造函数 运算符重载 --运算符重载 !运算符重载 运算符重载 *运算符重载 …

目录

1、list介绍

所要实现类及其成员函数接口总览 

2、结点类的模拟实现 

基本框架

构造函数

3、迭代器类的模拟实现

迭代器类存在的意义

3.1、正向迭代器 

基本框架

默认成员函数

构造函数

++运算符重载

--运算符重载 

!=运算符重载

==运算符重载 

*运算符重载 

->运算符重载 

3.2、反向迭代器

4、list类的模拟实现

基本框架

4.1、默认成员函数 

构造函数

拷贝构造函数

赋值运算符重载函数 

析构函数

4.2、迭代器相关函数

begin和end

rbegin和rend

4.3、访问容器相关函数 

front和back

4.4、增加的相关函数 

insert

push_back尾插

push_front头插 

4.5、删除的相关函数 

erase

pop_back尾删 

pop_front头删 

4.6、其他函数

size

resize

clear 

empty

empty_init空初始化

swap交换 


1、list介绍

在STL的底层实现当中,list其实就是一个带头双向循环链表:

我们现在要模拟实现list,要实现以下三个类:

  1. 模拟实现结点类
  2. 模拟实现迭代器的类
  3. 模拟list主要功能的类

第三个类的实现是基于前两个类。我们依次递进进行讲解。 


所要实现类及其成员函数接口总览 

namespace Fan
{//模拟实现list当中的结点类template<class T>struct _list_node{//成员函数_list_node(const T& val = T()); //构造函数//成员变量T _data;                  //数据域_list_node<T>* _next;    //后驱指针_list_node<T>* _prev;    //前驱指针}; //模拟实现list迭代器template<class T,class Ref,class Ptr>struct _list_iterator{typedef _list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;_list_iterator(Node*node);  //构造函数//各种运算符重载函数self operator++();self operator--();self operator++(int);self operator--(int);bool operator==(const self& s)const;bool operator!=(const self& s)const;Ref operator*();Ptr operator->();//成员变量Node* _node; //一个指向结点的指针};//模拟实现listtemplate<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;//默认成员函数list();list(const list<T>& lt);list<T>& operator=(const list<T>& lt);~list();//迭代器相关函数iterator begin();iterator end();const_iterator begin()const;const_iterator end()const;//访问容器相关函数T& front();T& back();const T& front()const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& x);iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其它函数size_t size()const;void resize(size_t n, const T& val = T());void clear();bool empty()const;void swap(list<T>& lt);private:Node* _head; //指向链表头结点的指针};
}

2、结点类的模拟实现 

基本框架

因为list的本质为带头双向循环链表,所以我们要确保其每个结点有以下成员:

  1. 前驱指针
  2. 后继指针
  3. data值存放数据
//模拟实现list当中的结点类template<class T>struct _list_node{//成员变量T _data;                  //数据域_list_node<T>* _next;    //后驱指针_list_node<T>* _prev;    //前驱指针}; 

构造函数

对于结点类的成员函数,我们只需要实现一个构造函数即可。结点的释放则由list默认生成的析构函数来完成。

//构造函数
_list_node(const T& val=T()):_data(val),_next(nullptr),_prev(nullptr)
{}

3、迭代器类的模拟实现

迭代器类存在的意义

我们知道list是带头双向循环链表,对于链表,我们知道其内存空间并不是连续的,是通过结点的指针顺次链接。而string和vector都是将数据存储在一块连续的内存空间,我们可以通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector的迭代器都是原生指针。而对于list,其各个结点在内存中的位置是随机的,并不是连续的,我们不能通过结点指针的自增、自减以及解引用等操作来修改对应结点数据。为了使得结点指针的各种行为和普通指针一样,我们对结点指针进行封装,对其各种运算符进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器


3.1、正向迭代器 

基本框架

//模拟实现list迭代器
template<class T,class Ref,class Ptr>
struct _list_iterator
{typedef _list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;//成员变量Node* _node; //一个指向结点的指针
};
  • 注意:

我们这里迭代器类的模板参数里面包含了3个参数:

template<class T,class Ref,class Ptr>

在后文list类的模拟实现中,我对迭代器进行了两种typedef:

typedef _list_iterator<T, T&, T*> iterator;//普通迭代器
typedef _list_iterator<T, const T&, const T*> const_iterator;//const迭代器

根据这里的对应关系:Ref对应的是&引用类型,Ptr对应的是*指针类型。当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。提高代码复用性。


默认成员函数

这里的默认成员函数我们只需要写构造函数。

  • 析构函数—结点并不属于迭代器,不需要迭代器释放
  • 拷贝构造—编译器默认生成的浅拷贝即可
  • 赋值重载—编译器默认生成的浅拷贝即可

构造函数

我们这里通过结点的指针即可完成构造。

//构造函数
_list_iterator(Node* node):_node(node)
{}

++运算符重载

++运算符非为前置++和后置++

  • 前置++

迭代器++的返回值还是迭代器。对于结点指针的前置++,我们就应该先让结点指针指向后一个结点,然后返回“自增”后的结点指针。

//前置++
self& operator++()
{_node = _node->_next; //直接让自己指向下一个结点即可实现++return *this;         //返回自增后的结点指针
}
  • 后置++

为了和前置++进行区分,后置++通常需要加上一个参数。此外,后置++是返回自增前的结点指针。

//后置++
self operator++(int) //加参数以便于区分前置++
{self tmp(*this);      //拷贝构造tmp_node = _node->_next; //直接让自己指向下一个结点即可实现++return tmp;
}

--运算符重载 

--运算符分为前置--和后置--

  • 前置--

前置--是让结点指针指向上一个结点,然后再返回“自减”后的结点指针即可。

//前置--
self operator--()
{_node = _node->_prev; //让结点指针指向前一个结点return *this;         //返回自减后的结点指针
}
  • 后置--

先记录当前结点指针的指向,然后让结点指针指向前一个结点,最后返回“自减”前的结点指针即可。

//后置--
self operator--(int) //加参数以便于区分前置--
{self tmp(*this); //拷贝构造tmp_node = _node->_prev;return tmp;
}

!=运算符重载

这里的比较是两个迭代器的比较,我们直接返回两个结点的位置是否不同即可。

//!=运算符重载
bool operator!=(const self& it)
{return _node != it._node; //返回两个结点指针的位置是否不同即可
}

==运算符重载 

我们直接返回两个结点指针是否相同即可。

//==运算符重载
bool operator==(const self& it)
{return _node == it._node; //返回两个结点指针是否相同
}

*运算符重载 

当我们使用解引用操作符时,是想要得到该位置的数据内容。因此我们直接返回结点指针指向的_data即可。

//*运算符重载
Ref operator*() //结点出了作用域还在,我们用引用返回
{return _node->_data; //返回结点指向的数据
}

->运算符重载 

假设出现此类情形,我们链表中存储的不是内置类型,而是自定义类型,如下:

struct AA
{AA(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;
};
void test()
{Fan::list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));lt.push_back(AA(4, 4));
}

对于内置类型和自定义类型成员的指针,其访问方式是不同的:

int*  *it
AA*   (*it). 或者 it->

这里我们应该重载一个->运算符。以便于访问自定义类型成员的指针的数据。

//->运算符重载
Ptr operator->()
{return &(operator*()); //返回结点指针所指向的数据的地址//或者return &_node->_data;
}

实现了->运算符重载后,我们执行it->_a1,编译器就将其转换成it.operator->(),此时获得的是结点位置的地址即AA*,这里应该还有一个箭头->才能获取数据,也就是这样:it.operator->()->_a1

  • 编译器为了可读性将其进行优化处理,如果不进行优化应该是it->->a1,优化以后省略了一个箭头->。

3.2、反向迭代器

反向迭代器是一种适配器模式(后面我们会讲到适配器)。相比于正向迭代器,反向迭代器主要有以下三种变化。

  • 反向迭代器里面的++执行的操作是正向迭代器里面的--。
  • 反向迭代器里面的--执行的操作是正向迭代器里面的++。
  • 反向迭代器里面的*解引用和->操作指向的是前一个数据。

反向迭代器是一种适配器模式。任何容器的迭代器封装适配一下都能够生成对应的反向迭代器。

反向迭代器里面的*解引用和->操作指向的是前一个数据。其目的主要是为了对称设计。在代码实现当中,rbegin函数对应的是end函数,rend函数对应的是begin函数。

代码如下:

namespace Fan
{template<class Iterator,class Ref,class Ptr>struct Reverse_iterator{Iterator _it;typedef Reverse_iterator<Iterator, Ref, Ptr> Self;//构造函数Reverse_iterator(Iterator it):_it(it){}//*运算符重载Ref operator*(){Iterator tmp = _it;//返回上一个数据return *(--tmp);}//->运算符重载Ptr operator->(){//复用operator*,返回上一个数据return &(operator*());}//++运算符重载Self& operator++(){--_it;return *this;}Self operator++(int){Iterator tmp = _it;--_it;return tmp;}//--运算符重载Self& operator--(){++_it;return *this;}Self operator--(int){Iterator tmp = _it;++_it;return tmp;}//!=运算符重载bool operator!=(const Self& s){return _it != s._it;}//==运算符重载bool operator==(const Self& s){return _it == s._it;}};
}

4、list类的模拟实现

基本框架

在list类中的唯一一个成员变量即为先前的结点类构成的头结点指针:

//模拟实现list
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;  //const迭代器//反向迭代器typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;private:Node* _head; //指向链表头结点的指针
};

4.1、默认成员函数 

构造函数

  • 无参构造:

list是一个带头双向循环链表,在构造一个list对象时,我们直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可。

//构造函数
list()
{_head = new Node();//申请一个头结点_head->_next = _head;//头结点的下一个结点指向自己构成循环_head->_prev = _head;//头结点的上一个结点指向自己构成循环
}
  • 传迭代器区间构造:

先进行初始化,然后利用循环对迭代器区间的元素挨个尾插。

//传迭代器区间构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{empty_init();while (first != last){push_back(*first);first++;}
}

拷贝构造函数

假设我们要用lt1去拷贝构造lt2。

  • 传统写法:

我们首先复用empty_init对头结点进行初始化,接着遍历lt1的元素,在遍历的过程中将lt1的元素尾插到lt2上即可。接着使用push_back自动开辟空间完成深拷贝。

//传统写法
list(const list<T>& lt)
{//先初始化lt2empty_init();//遍历lt1,把lt1的元素push_back到lt2里面for (auto e : lt){push_back(e); //自动开辟新空间,完成深拷贝}
}
  • 现代写法:

这里我们先初始化lt2,然后把lt1引用传参传给lt,传lt的迭代器区间构造tmp,复用swap交换头结点指针即可完成深拷贝的现代写法。

//现代写法
list(const list<T>& lt)
{//先进行初始化empty_init();list<T>tmp(lt.begin(), lt.end());  //用迭代器区间去构造tmpswap(tmp);
}

赋值运算符重载函数 

对于赋值运算符的重载,我们仍然提供两种写法:

  • 传统写法

先调用clear函数将原容器清空,然后再将lt当中的数据通过遍历的方式一个个尾插到清空后的容器当中即可。

//传统写法
list<T>& operator=(const list<T>& lt)
{if (this != &lt) //避免自己给自己赋值{clear();  //清空容器for (const auto& e : lt){push_back(e); //将容器lt当中的数据一个个尾插到链表后面}}return *this; //支持连续赋值
}
  • 现代写法

利用编译器机制,故意不使用引用传参,通过编译器自动调用list的拷贝构造函数构造出一个list对象lt,然后调用swap函数将原容器与该list对象进行交换即可。

//现代写法
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{swap(lt); //交换这两个对象return *this; //支持连续赋值
}

析构函数

我们可以先复用clear函数把除了头结点的所有结点给删除掉,最后delete头结点即可。

//析构函数
~list()
{clear();delete _head; //删去哨兵位头结点_head = nullptr;
}

4.2、迭代器相关函数

begin和end

  • begin的作用是返回第一个位置的结点的迭代器,而第一个结点就是哨兵位头结点的下一个结点。因此我们直接返回_head的_next即可。
  • end的作用是返回最后一个有效数据的下一个位置的迭代器,对于list指的就是哨兵位头结点_head的位置。

begin和end均分为普通对象调用和const对象调用,因此我们要写两个版本。

  • 普通对象调用
//begin
iterator begin() //begin返回的就是第一个有效数据,即头结点的下一个结点
{return iterator(_head->_next);//return _head->_next;
}//end
iterator end()
{return iterator(_head);//return _head;
}
  • const对象调用
//begin
const_iterator begin() const
{return const_iterator(_head->_next);//return _head->_next; 
}
//end
const_iterator end() const
{return const_iterator(_head);//return _head;  也可以这样写
}

rbegin和rend

rbegin就是正向迭代器里的end()位置,rend就是正向迭代器里的begin()位置。

rbegin和rend同样分为普通对象调用和const对象调用:

  • 普通对象调用 
//rbegin()
reverse_iterator rbegin()
{return reverse_iterator(end());
}
//rend
reverse_iterator rend()
{return reverse_iterator(begin());
}
  • const对象调用
//const反向迭代器
const_reverse_iterator rbegin() const
{return const_reverse_iterator(end());
}
const_reverse_iterator rend() const
{return const_reverse_iterator(begin());
}

4.3、访问容器相关函数 

front和back

front和back函数分别用于获取第一个有效数据和最后一个有效数据,因此在实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。

  • 普通对象调用
//front
T& front()
{return *begin(); //直接返回第一个有效数据的引用
}
T& back()
{return *(--end()); //返回最后一个有效数据的引用
}
  • const对象调用
const T& front() const
{return *begin(); //直接返回第一个有效数据的引用
}
const T& back() const
{return *(--end()); //返回最后一个有效数据的引用
}

4.4、增加的相关函数 

insert

实现insert首先创建一个新的结点存储插入的值,接着取出插入位置pos处的结点指针保存在cur里面,记录cur的上一个结点位置prev,先衔接prev和newnode,再链接newnode和cur即可,最后返回新插入元素的迭代器位置。

  • list的insert不存在野指针失效的迭代器失效问题。
//头插
void push_front(const T& x)
{insert(begin(), x);
}
//insert,插入pos位置之前
iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);//创建新的结点Node* cur = pos._node; //迭代器pos处的结点指针Node* prev = cur->_prev;//prev newnode cur//链接prev和newnodeprev->_next = newnode;newnode->_prev = prev;//链接newnode和curnewnode->_next = cur;cur->_prev = newnode;//返回新插入元素的迭代器位置return iterator(newnode);
}

push_back尾插

  • 法一

首先要创建一个新结点用来存储尾插的值,接着找到尾结点。将尾结点和新结点前后链接并将头结点和新结点前后链接构成循环即可。

//尾插
void push_back(const T& x)
{Node* tail = _head->_prev; //找尾Node* newnode = new Node(x); //创建一个新的结点//_head tail newnode//链接tail和newnodetail->_next = newnode;newnode->_prev = tail;//链接newnode和头结点_headnewnode->_next = _head;_head->_prev = newnode;
}
  • 法二

这里也可以直接复用insert函数,当insert中的pos位置为哨兵位头结点的位置时,实现的就是尾插。

//尾插
void push_back(const T& x)
{//法二:复用insertinsert(end(), x);
}

push_front头插 

直接复用insert函数,当pos位置为begin()时,获得的pos就是第一个有效结点数据,即可满足头插。

//头插
void push_front(const T& x)
{insert(begin(), x);
}

4.5、删除的相关函数 

erase

erase删除的是pos位置的结点。我们首先取出pos位置的结点指针cur,记录cur上一个结点位置为prev,再记录cur下一个结点位置为next,链接prev和next,最后delete释放掉cur的结点指针即可。返回删除元素后一个元素的迭代器位置。

//erase
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev cur next//链接prev和nextprev->_next = next;next->_prev = prev;//delete要删除的结点delete cur;//返回删除元素后一个元素的迭代器位置//return next;return iterator(next);
}

pop_back尾删 

直接复用erase即可,当pos位置为--end()时,pos就是最后一个结点的位置,实现的就是尾删。

//尾删
void pop_back()
{erase(--end());
}

pop_front头删 

直接复用erase即可,当pos位置为begin()时,pos就是第一个有效数据,实现的就是头删。

//头删
void pop_front()
{erase(begin());
}

4.6、其他函数

size

size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以我们只能通过遍历的方式逐个统计有效数据的个数。

//size
size_t size()const
{size_t sz = 0; //统计有效数据个数const_iterator it = begin(); //获取第一个有效数据的迭代器while (it != end()) //通过遍历统计有效数据个数{sz++;it++;}return sz; //返回有效数据个数
}

resize

 resize函数的规则:

  • 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  • 若当前容器的size大于所给n,则只保留前n个有效数据。

当我们实现resize函数时,我们不要直接调用size函数获取当前容器的有效数据个数,因为当我们调用resize函数后就已经遍历了一次容器。如果结果是size大于n,那么还需要遍历容器,找到第n个有效结点并释放之后的结点。

这里实现resize的方法是:设置一个变量len,用于记录当前所遍历的数据个数,然后开始遍历容器,在遍历的过程中:

  1. 当len大于或者是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
  2. 遍历完容器,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。
void resize(size_t n, const T& val = T())
{iterator i = begin();  //获取第一个有效数据的迭代器size_t len = 0;  //记录当前所遍历的数据个数while (len < n && i != end()){len++;i++;}if (len == n) //说明容器当中的有效数据个数大于或者是等于n{while (i != end()) //只保留前n个有效数据{i = erase(i); //接收下一个数据的迭代器}}else  //说明容器当中的有效数据个数小于n{while (len < n){push_back(val);len++;}}
}

clear 

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

void clear()
{iterator it = begin();while (it != end()){it = erase(it); //用it接收删除后的下一个结点的位置}
}

empty

empty函数用于判断容器是否为空,我们直接判断该容器的begin函数和end函数所返回的迭代器是否是同一个位置的迭代器即可。(此时说明容器当中只有一个头结点)

bool empty()const
{return begin() == end(); //判断是否只有头结点
}

empty_init空初始化

 该函数的作用是哨兵位的头结点开出来,再对其进行初始化。该函数是库里面的。

//空初始化  对头结点进行初始化
void empty_init()
{_head = new Node();_head->_next = _head;_head->_prev = _head;
}

swap交换 

对于链表的swap,我们直接交换头结点指针的指向即可完成。直接复用库函数的swap即可。

//swap交换函数
void swap(list<T>& lt)
{std::swap(_head, lt._head);//交换头指针
}

相关文章:

【STL】模拟实现list

目录 1、list介绍 所要实现类及其成员函数接口总览 2、结点类的模拟实现 基本框架 构造函数 3、迭代器类的模拟实现 迭代器类存在的意义 3.1、正向迭代器 基本框架 默认成员函数 构造函数 运算符重载 --运算符重载 !运算符重载 运算符重载 *运算符重载 …...

Spring Cloud Alibaba全家桶(五)——微服务组件Nacos配置中心

前言 本文小新为大家带来 微服务组件Nacos配置中心 相关知识&#xff0c;具体内容包括Nacos Config快速开始指引&#xff0c;搭建nacos-config服务&#xff0c;Config相关配置&#xff0c;配置的优先级&#xff0c;RefreshScope注解等进行详尽介绍~ 不积跬步&#xff0c;无以至…...

【微信小程序】-- 页面事件 - 下拉刷新(二十五)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

springboot启动过程加载数据笔记(springboot3)

SpringApplication AbstractApplicationContext PostProcessorRegistrationDelegate ConfigurationClassPostProcessor ConfigurationClassParser 一堆循环和调用 ComponentScanAnnotationParser扫描 processConfigurationClass.doProcessConfigurationClass(configClass, so…...

中文代码86

PK 嘚釦 docProps/PK 嘚釦諿A眎 { docProps/app.xml漅薾?糤?D?v拢W4揣狤"攃e9 睔貣m*:PAz韒g?项弇}R珁湧4嶱 ]I禑菦?櫮戵\U佳 珩 ]铒e礎??X(7弅锿?jl筀儸偛佣??z窊梈ZT炰攷 ?\ 銒沆?状尧绥>蕮 ?斬殕{do]?o乗YX?:??罢秗,泿)怟 …...

网络参考模型

OSI参考模型 应用层 不服务于任何其他层&#xff0c;就是位APP提供相应的服务&#xff0c;不如HTTP、域名解析DNS提供服务表示层 1.使得应用数据能够被不同的系统&#xff08;Windows\Linux&#xff09;进行识别和理解 2.数据的解码和编码、数据的加密与解密、数据的压缩和解…...

Spark Tungsten

Spark Tungsten数据结构Unsafe Row内存页管理全阶段代码生成火山迭代模型WSCG运行时动态生成Tungsten (钨丝计划) : 围绕内核引擎的改进&#xff1a; 数据结构设计全阶段代码生成&#xff08;WSCG&#xff0c;Whole Stage Code Generation&#xff09; 数据结构 Tungsten 在…...

2023年总结的web前端学习路线分享(学习导读)

如果你打开了这篇文章&#xff0c;说明你是有兴趣想了解前端的这个行业的&#xff0c;以下是博主2023年总结的一些web前端的学习分享路线&#xff0c;如果你也想从事前端或者有这方面的想法的&#xff0c;请接着往下看&#xff01; 前端发展前景 前端入门 巩固基础 前端工程…...

MyBatis学习笔记(十) —— 动态SQL

10、动态SQL MyBatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能&#xff0c;它存在的意义是为了解决拼接SQL语句字符串的痛点问题。 动态SQL&#xff1a; 1、if 标签&#xff1a;通过test属性中的表达式判断标签中的内容是否有效&#xff08;是否会拼接到sql中…...

剑指 Offer 55 - II. 平衡二叉树

剑指 Offer 55 - II. 平衡二叉树 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 输入一棵二叉树的根节点&#xff0c;判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1&#xff0c;那么它就是一棵平衡二叉树。 示例 1: 给定二叉树 […...

一文吃透前端低代码的 “神仙生活”

今天来说说前端低代码有多幸福&#xff1f; 低代码是啥&#xff1f;顾名思义少写代码…… 这种情况下带来的幸福有&#xff1a;代码写得少&#xff0c;bug也就越少&#xff08;所谓“少做少错”&#xff09;&#xff0c;因此开发环节的两大支柱性工作“赶需求”和“修bug”就…...

【深度学习】预训练语言模型-BERT

1.BERT简介 BERT是一种预训练语言模型&#xff08;pre-trained language model, PLM&#xff09;&#xff0c;其全称是Bidirectional Encoder Representations from Transformers。下面从语言模型和预训练开始展开对预训练语言模型BERT的介绍。 1-1 语言模型 语言模型 &#xf…...

C++类的组合

C类的组合什么是类的组合初始化参数列表使用类的组合案例分析组合构造和析构顺序问题this指针基本用法和作用什么是类的组合 类的组合就是以另一个对象为数据成员&#xff0c;这种情况称为类的组合 1.优先使用类的组合&#xff0c;而不是继承 2.组合表达式的含义 一部分关系 初…...

2.伪随机数生成器(ctr_drbg)的配置与使用

零、随机数应用 生成盐,用于基于口令的密码 生成密钥,用于加密和认证 生成一次性整数Nonce,防止重放攻击 生成初始化向量IV 构成 种子,真随机数生成器的种子来源于物理现象 内部状态,种子用来初始化内部状态 一、真随机数和伪随机数 1.区别 随机数在安全技术中通常被用于…...

CentOS7 切换图形模式和多用户命令行模式

备注&#xff1a; 主机名 hw 含义&#xff1a;hardware 缩写&#xff0c;意思是硬件&#xff08;物理机&#xff09; 文章目录1、查看源头2、查看当前系统运行模式3、设置系统运行模式为多用户命令行模式4、查看当前系统运行模式5、重启系统6、确认当前系统运行模式7、设置系统…...

在linux上用SDKMan对Java进行多版本管理

在linux上用SDKMan对Java进行多版本管理 有一个工具叫SDKMan&#xff0c;它允许我们这样做。官方网站这样描述: TIP: "SDKMan 是一个工具&#xff0c;用于在大多数基于Unix的系统上管理多个软件开发工具包的并行版本。它提供了一个方便的命令行接口(CLI)和API&#xff0c…...

JSONObject、fastJson(JsonObject)、Gson(JsonObject)区别

概述 Java中并没有内置的 JSON 解析&#xff0c;需要使用第三方类库 fastJson &#xff1a;阿里巴巴的JSON 库&#xff0c;优势在于解析速度快&#xff0c;解析效率高&#xff0c;可以轻松处理大量的 JSON 数据JackSon &#xff1a; 社区十分活跃&#xff0c;spring框架默认使…...

如何在CSDN中使用ChatGPT

本篇文章致力于帮助大家理解和使用ChatGPT&#xff08;现在CSDN改成”C知道“了&#xff09;。简介ChatGPT是OpenAI公司开发的一种大型语言模型。它是一种基于Transformer架构的深度学习模型&#xff0c;可以对语言进行建模和生成。它可以处理问答、对话生成、文本生成等多种任…...

【Spring6】| GoF之工厂模式

目录 一&#xff1a;GoF之工厂模式 1. 工厂模式的三种形态 2. 简单工厂模式 3. 工厂方法模式 4. 抽象工厂模式&#xff08;了解&#xff09; 一&#xff1a;GoF之工厂模式 &#xff08;1&#xff09;GoF&#xff08;Gang of Four&#xff09;&#xff0c;中文名——四人组…...

初识Node.js

文章目录初识Node.jsNode.js简介fs模块演示路径问题path路径模块http模块创建web服务器得基本步骤req请求对象res响应对象解决中文乱码问题模块化的基本慨念1、模块化2、Node.js中模块的分类3、Node.js中的模块作用域3.1什么是模块作用域4、向外共享模块作用域中的成员4.1modul…...

C51---软件消抖

1.example #include "reg52.h" #include "intrins.h" //main.c(11): error C264: intrinsic _nop_: declaration/activation error,添加这个头文件就可了 sbit led1 P3^7;//引脚位置&#xff0c;根据原理图可知 sbit key1 P2^1; sbit key2 P2^0; void …...

redis数据持久化

redis备份概念 Redis所有数据都是保存在内存中&#xff0c;Redis数据备份可以定期的通过异步方式保存到磁盘上&#xff0c;该方式称为半持久化模式&#xff0c;如果每一次数据变化都写入aof文件里面&#xff0c;则称为全持久化模式。同时还可以基于Redis主从复制实现Redis备份…...

Java StringBuffer类

Java StringBuffer类是Java语言中一个非常重要的类&#xff0c;它提供了丰富的方法&#xff0c;可以方便地进行字符串操作。本文将详细介绍Java StringBuffer类的作用以及在实际工作中的用途。 StringBuffer类的作用 Java StringBuffer类是一个可变的字符串缓冲区&#xff0c…...

电路模型和电路定律(2)——“电路分析”

各位CSDN的uu们你们好呀&#xff0c;好久没有更新电路分析的文章啦&#xff0c;今天来小小复习一波&#xff0c;之前那篇博客&#xff0c;小雅兰更新了电路的历史以及电压电流的参考方向&#xff0c;这篇博客小雅兰继续&#xff01;&#xff01;&#xff01; 电阻元件 电压源和…...

天琊超级进程监视器的应用试验(19)

实验目的 1、了解进程概念及其基本原理&#xff1b; 2、掌握天琊超级进程监视器的安装与使用。预备知识 本实验要求实验者具备如下的相关知识。 操作系统的安全配置是整个系统安全审计策略核心&#xff0c;其目的就是从系统根源构筑安全防护体系&#xff0c;通过用户的一…...

使用 Pulumi 打造自己的多云管理平台

前言在公有云技术与产品飞速发展的时代&#xff0c;业务对于其自身的可用性提出了越来越高的要求&#xff0c;当跨区域容灾已经无法满足业务需求的情况下&#xff0c;我们通常会考虑多云部署我们的业务平台&#xff0c;以规避更大规模的风险。但在多云平台部署的架构下&#xf…...

什么是MyBatis?无论是基础教学还是技术精进,你都应该看这篇MyBatis

文章目录学习之前&#xff0c;跟你们说点事情&#xff0c;有助于你能快速看完文章一、先应用再学习&#xff0c;代码示例1. 第一个MyBatis程序2. MyBatis整合Spring3. SpringBoot整合MyBatis二、MyBatis整体流程&#xff0c;各组件的作用域和生命周期三、说说MyBatis-config.xm…...

【编程基础之Python】10、Python中的运算符

【编程基础之Python】10、Python中的运算符Python中的运算符算术运算符赋值运算符比较运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级运算符总结Python中的运算符 Python是一门非常流行的编程语言&#xff0c;它支持各种运算符来执行各种操作。这篇文章将详细介绍…...

Android的基础介绍

一、Android介绍 Android是一种基于Linux的自由及开放源代码的操作系统,Android 分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和Linux内核层。 Android 是Google开发的基于Linux平台的开源手机操作系统。它包括操作系统、用户界面和应用程序——…...

用户登录请求100w/每天, JVM如何调优

用户登录请求100w/每天, JVM如何调优 大概可以分为以下8个步骤。 Step1&#xff1a;新系统上线如何规划容量&#xff1f; 1.套路总结 任何新的业务系统在上线以前都需要去估算服务器配置和JVM的内存参数&#xff0c;这个容量与资源规划并不仅仅是系统架构师的随意估算的&am…...