list模拟实现
list模拟实现
- list原理讲解
- 节点结构
- list类设计
- push_back
- Iterators:
- begin与end
- const对象的迭代器
- 重载->运算符
- 反向迭代器
- 迭代器所有代码
- 迭代器总结
- constructor:
- clear
- ~list
- front与back
- insert与erase
- size与empty与swap
- pop_back()
- 总代码:
- 节点类
- 正向迭代器类
- 反向迭代器类
- list类
list原理讲解
在C++中SGI版本的STL中的list容器是用带头双向循环链表来实现的;
那么什么是带头双向循环链表?
如下图:
该链表有一个head头节点,该节点不存储任何有效数据!仅用于链接第一个有效节点和最后一个有效节点;head的前一个节点就是该链表的最后一个有效节点,head的后一个节点,就是该链表的第一个有效节点!
只要我们能够找到head节点,我们就能找到整条带头双向循环链表!
使用该结构存储数据的话,插入和删除数据都非常的快,时间复杂度为O(1)
相比于前面的vector来说vector的插入和删除数据效率都比较低!当我们需要进行大量的插入和删除数据的时候我们可以考虑使用list数据结构!
节点结构
该节点,有数据域和指针域,其中数据域专门用来存储数据,指针域细分的话,又有两个指针域:前指针域(用于存储前一个节点的指针)、后指针域(用于存储后一个节点的指针);
因此我们可以这样设置节点数据结构:
//节点template <class T>struct list_node{T _date;list_node<T>* _prev;list_node<T>* _next;list_node(const T& val = T())//这里可以考虑使用缺省参数;//由于模板的出现,这就要求了对于内置类型必须提供构造函数,不然的话对于这种情况const T& val = T()我们无法处理,因为我们不知道T的具体类型是什么,无法准确的给缺省值!同时const引用可以延长匿名对象的生命周期,直到引用所在作用域销毁:_date(val), _prev(nullptr), _next(nullptr) {}};
我们可以用一个模板来写一个节点数据结构,因为节点的数据域可以存储任何类型的数据,同时我们可以为该节点提供一个构造函数,方便后续向节点插入数据的需求!
同时这里我们用了struct来创建节点类,不用class,是因为struct创建的类的默认权限是public更为方便我们后续的操作,class创建的类的默认权限是private的,当我们将类内部的成员全部放开时,我们可以利用struct来创建类!当然也可以用class,只不过这时候我们需要加上public访问限定符!
list类设计
list类也应该成一个模板类,因为list可以插入任何类型的元素!
template <class T>
{
public:
private:
//list的成员我们可以设计为一个list_node<T>*的指针
//该指针用于存储带头双向循环链表的head节点;
//只要我们找到了head节点,我们就能找到整条链表!
//list_node<T> * _head;//当然这里我们可以考虑typedef一下list_node<T>,毕竟这个类型太长了,写起来比较麻烦,当然我们也可以不typedef,直接使用原生类型
typedef list_node<T> node;
node*_head;
}
既然list类成员已经设计出来了,那么我们现在就创建一个带头双向循环链表来玩一玩;
首先要有一个带头双向循环链表,我们就得现有一个head节点,方便我们list对于整条链表的管理;
因此我们的构造函数可以写成:
list()
{
_head=new node;//创建一个头节点
_head->_prev=_head->_next=_head;//让其头节点的_next指针和_prev指针都指向自己,因为现在一个元素也没有,_head的下一个节点就是自己,_head的前一个节点也是自己
}
push_back
在上面,我们已经实现出list的一个简单无参构造函数,我们现在就有了_head节点,我们现在就只需要插入元素了,对于插入元素来说很简单:
要实现尾插,就需要找到最后一个一个节点,那么最后一个节点在哪里?
_head->_prev不就是最后一个节点嘛,最后在将new_node节点的_next域链接向_head节点,_head的_prev域链接向new_node节点就可以了;
void push_back(const T&val)
{node*new_node=new node(val);node*tail=_head->_prev;//尾节点tail->_next=new_node;new_node->_prev=tail;new_node->_next=_head;_head->_prev=new_node;
}
Iterators:
现在我们已经可以向list里面插入元素了,但是我们想打印一下list元素,以此来检查一下我们push_back的正确性!
为此我们就要遍历整个链表,遍历的话我们就需要迭代器,可是现在有个问题,我们应该拿什么作为迭代器呢?
原生的节点指针(list_node<T>*)吗?
如果是这样的话,那么迭代器该如何往后迭代呢?要知道迭代器只允许使用++、–等迭代方式,是不允许->这样的迭代方式的,但是我们使用++、–等方式对我们的迭代器进行迭代的话,我们能保证it++就是下一个节点的指针吗?这显然是无法保证的,因为节点与节点之间都是不连续的,it++自然也就无法保证++过后就一定是下一个节点的指针!但凡list的结构是类似于vector、string的连续空间我们都可以使用原生指针来充当迭代器!但对于list不行,因此list中使用原生指针作为迭代器的想法被直接pass掉!
那么有没有什么既可以指向对应节点,同时又能使用++、–来进行迭代的东西呢?
当然有,我们可以对原生指针进行封装!封装成一个类,让这个类充当迭代器,然后在这个类内部重载++、–等运算符,以此来实现迭代器的迭代;
事不宜迟,咱们现在就来干一个:
template <class T//元素类型>
struct _list_iterator
{ //用于初始化迭代器_list_iterator(list_node<T>* node=nullptr):_node(node){}//由于_list_iterator<T> 这个迭代器类型使用起来比较麻烦,有需要大量的使用,我们这里解直接typedef一下;typedef _list_iterator<T> self;//self就表示迭代器的类型,本质还是_list_iterator<T>//开始重载++、--、*、!=等运算符self&operator++()//前置++{assert(_node);//防止_node为nullptr,避免出现这种情况:_list_iterator it;//此时it会调用无参构造,_node会被初始化成nullptr,这时候如果再使用++it就会对nullptr解引用,会崩溃_node=_node->_next;return *this;}self operator++(int)//后置++{assert(_node);self tmp(*this);//这里可以使用默认拷贝构造,因为在迭代器类中不会对节点进行析构++(*this);return tmp;}T& operator*()//对迭代器解引用,返回数据域的值{assert(_node);return _node->_date;}bool operator!=(const self&it)//两个迭代器之间进行比较{//这也是迭代器继续迭代比较运算符//对于list这类迭代器,就不在适合使用>、<等运算符进行比较了,因为节点不是连续的return _node!=it._node;}self&operator--()//前置--{assert(_node);_node=_node->_prev;return *this;}self operator++(int)//后置--{assert(_node);self tmp(*this);//这里可以使用默认拷贝构造,因为在迭代器类中不会对节点进行析构--(*this);return tmp;}//成员变量list_node<T> *_node;//用于存储当前迭代器所迭代的位置
}
实现了迭代器,我们就可以在list类内部使用了,不过我们需要先将我们实现的迭代器的名字在list类内部typedef一下,因为STL的所有容器的迭代器都是iterator,这算是一种默认的规范!
在list内部加上:
typedef _list_iterator<T> iterator;
begin与end
有了迭代器在list内部的begin与end函数就可以写了
iterator begin()
{
return iterator(_head->_next);
}
iteratro end()
{
return iterator(_head);
}
const对象的迭代器
我们上面实现的迭代器,还是有缺陷的,比如上面的begin与end我们只是实现了普通list对象,对于const对象也无法调用begin、end,换而言之const不能使用迭代器进行迭代,只有普通list对象可以;
那么有读者就说,const对象调不了,begin、end是因为begin、end的this指针权限过大,我们在重载一个const对象的begin、end不就好了:
iterator begin()const
{
return iterator(_head->_next);
}
iteratro end()const
{
return iterator(_head);
}
嗯,这样的话const对象确实能够进行迭代了,可是新问题又来了,(*迭代器)的时候,也能改变迭代器所指节点的值!因为operator*()的返回值是T&,嗯???这似乎与我们的预期相违背了,const对象的值怎么能通过迭代去修改呢?我们必须解决这个问题;
我们可以不可在普通迭代器前面加个const来充当const对象的迭代器呢?
比如:
typedef _list_iteratro iterator;
//iterator不是普通对象的迭代器嘛
//那么const对象的迭代器可不可以是:
typedef const _list_iteratro const_iterator;
//const _list_iteratro 来充当const对象的迭代器?
//答:不可以!!!如果我们这样做了的话,我们迭代器本身就无法完成迭代了!
//迭代器本身都无法完成迭代,那还叫个屁的迭代器!
//这就好比,int a=10;与const int b=10;
//a可以实现++、--,但是b就不可以!
怎么解决呢?
1、重新为const对象协议一个迭代器,其中operator*()的返回值设置为const T &;
2、按照方法1确实可以解决问题,可是太麻烦了,你看嘛我们const迭代器与普通对象的迭代器都需要实现++、–等迭代操作,但是这两个迭代器的唯一区别就是operator*()的返回值,const迭代器是const T& ,而普通迭代器是T& 二者之间就差一个const,要是我们能用一个“变量”来控制operator*的返回值就好了!
那么能不能实现呢?
当然可以,我们可以在_list_iterator模板参数列表,再添加一个参数Ref,以此来控制operator*()的返回值,但我们给_list_iterator类模板传入不同的参数时,_list_iterator类模板就会给我们实例化出不同的具体类型!
因此我们的迭代器可以优化成:
template <class T,class Ref>
struct _list_iterator
{ //用于初始化迭代器_list_iterator(list_node<T>* node=nullptr):_node(node){}//由于_list_iterator<T> 这个迭代器类型使用起来比较麻烦,有需要大量的使用,我们这里解直接typedef一下;typedef _list_iterator<T,Ref> self;//self就表示迭代器的类型,本质还是_list_iterator<T,Ref>//开始重载++、--、*、!=等运算符self&operator++()//前置++{assert(_node);//防止_node为nullptr,避免出现这种情况:_list_iterator it;//此时it会调用无参构造,_node会被初始化成nullptr,这时候如果再使用++it就会对nullptr解引用,会崩溃_node=_node->_next;return *this;}self operator++(int)//后置++{assert(_node);self tmp(*this);//这里可以使用默认拷贝构造,因为在迭代器类中不会对节点进行析构++(*this);return tmp;}Ref operator*()//对迭代器解引用,返回数据域的值,根据Ref的不同,来决定operator\*()的返回值类型{assert(_node);return _node->_date;}bool operator!=(const self&it)//两个迭代器之间进行比较{//这也是迭代器继续迭代比较运算符//对于list这类迭代器,就不在适合使用>、<等运算符进行比较了,因为节点不是连续的return _node!=it._node;}self&operator--()//前置--{assert(_node);_node=_node->_prev;return *this;}self operator++(int)//后置--{assert(_node);self tmp(*this);//这里可以使用默认拷贝构造,因为在迭代器类中不会对节点进行析构--(*this);return tmp;}//成员变量list_node<T> *_node;//用于存储当前迭代器所迭代的位置
}
这样的话,再list内部我们就需要重新实例化一下普通对象与const对象的迭代器了:
typedef _list_iterator<T,T&> iterator;//普通list对象的迭代器
typedef _list_iterator<T,const T&> const_iterator;//const对象的迭代器
//由于传递的模板参数不同,_list_iterator<class T,class Ref>会实例化出两份不同类型的迭代器!
重载->运算符
对于list迭代器来说,我们可以说是模拟原生指针的行为,比如(*迭代器)返回的就是节点数据域的值!
那么如果现在有一个结构体:
struct AA
{
int _a;
int _b;AA(int a=0;int b=0):_a(a),_b(b){}
}
int main()
{list<int> l1;l1.push_back(AA(1,2));list<int> :: iterator it=l1.begin();//正常写法://std::cout<<(*it)._a<<" "<<(*it)._b<<std::endl;//可是现在我们嫌弃上面那种方法比较麻烦,我们可不可以直接使用下面这种方法来实现呢?std::cout<<it->_a<<" "<<it->_b<<std::endl;//答案是可以的,因此在迭代器内部,我们需要重载->运算符;
}
迭代器内部重载->运算符
T*operator->()
{
return &_node->date;
}
我们只有实现了上诉部分的运算符重载,我们才能直接使用:
std::cout<<it->_a<<" "<<it->_b<<std::endl;
语句;
可是我们仔细观察一下operator->的返回值就会发现很奇怪的地方,operator->的返回值是T*,那么正确访问_a成员的方式,不应该是:it->->_a 不应该是两个箭头吗?为什么这里只需要一个->就可以!这里的话就与重载->的特殊性有关了,建议观看大佬的这篇文章:C++箭头(->)重载函数这位大佬讲的很明白!
现在又有个问题,如果const对象的迭代使用operator->()函数的或是不是也会改变节点数据域的值?
因为operator->的返回值是T*,没有const限制!为此为了限制这一“漏洞”,我们可以效仿operator*()返回值的作法,在增加一个模板参数,那么_list_iterator就会被优化成:
template<class T,class Ref,class Ptr>
struct _list_iterator{_list_iterator(list_node<T>* node = nullptr):_node(node) {}typedef _list_iterator<T, Ref, Ptr > self;self& operator++(){_node = _node->_next;return *this;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp = *this;--*this;return tmp;}self operator++(int){self tmp = *this;++* this;return tmp;}bool operator!=(const self& it){return _node != it._node;}Ref operator*(){return _node->_date;}Ptr operator->(){return &_node->_date;}//成员list_node<T>* _node;};
在list内部,普通迭代器与const迭代器的原生类型就是:
typedef _list_iterator<T, T&, T*> iterator; //这是个普通对象的正向迭代器
typedef _list_iterator<T, const T&, const T*> const_iterator;//const对象的正向迭代器
反向迭代器
我们发现上面实现的迭代器,++操作就是向后迭代、–等操作就是向前迭代,妥妥的正向迭代器,可是反向迭代器嘞?
反向迭代器,也可以按照上面的方法来实现,只不过反向迭代器内部可以封装一个正向迭代器,正向迭代器的++就是反向迭代器的–;正向迭代器的–就是反向迭代器的++;
//反向迭代器template<class Forward_iterators, class Ref, class Ptr>//Forward_iterators是个正向迭代器,那么到底是普通对象的正向迭代器还是const对象的正向迭代器,我们是未知的,全靠程序员利用该模板实例化的时候,给该模板参数传递的类型;Ref、Ptr与上面在正向迭代器中的意义一样struct _list_reverse_iterator{_list_reverse_iterator(const Forward_iterators& it= Forward_iterators()):_it(it) {}typedef _list_reverse_iterator<Forward_iterators, Ref, Ptr> Reself;//反向迭代器类型名字太长了,换个简短点的Reself& operator++()//前置{//正向迭代器的----_it;return *this;}Reself operator++(int)//后置{Reself tmp(_it);--_it;return tmp;}Reself& operator--()//前置{//正向迭代器的--++_it;return *this;}Reself operator--(int)//后置{Reself tmp(_it);++_it;return tmp;}Ref operator*(){return *_it;}Ptr operator->(){return _it.operator->();}bool operator!=(const Reself&rit){return _it != rit._it;}bool operator==(const Reself& rit){return _it==rit;}//成员是一个正向迭代器;Forward_iterators _it;};
因此在list内部的我们需要实例化一下普通对象的反向迭代器、和const对象的反向迭代器:
typedef _list_reverse_iterator<iterator, T&, T*> reverse_iterator;//普通对象反向迭代器
typedef _list_reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;//const对象反向迭代器
//给_list_reverse_iterator<Forward_iterators ,Ref,Ptr>传递的模板参数不同,就能实例化出不同类型的反向迭代器;
迭代器所有代码
iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(_head->_prev);}reverse_iterator rend(){return reverse_iterator(_head);}const_reverse_iterator rbegin()const{return const_reverse_iterator(_head->_prev);}const_reverse_iterator rend()const{return const_reverse_iterator(_head);}
迭代器总结
迭代器主要有两种形式:
1、迭代器要么就是原生指针;
2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为;
constructor:
list这个容器,难点就在于迭代器,现在我们把迭代器给啃了,后面就好办了;
注意:在每次初始化list的时候都需要创建头节点,为此我们可以把创建头节点的操作封装在一个函数里面;
创建头节点:
void empty_init(){_head = new node;_head->_next = _head->_prev = _head;}
同时我们可以把该函数放在private作用域下,不对外开放!
无参构造:
list()
{empty_init();
}
有参构造(利用n个val来初始化):
list(int n, const T& val = T()){empty_init();for (int i = 0; i < n; i++){push_back(val);}}
区间构造(可以用任何容器的元素来进行构造,只要可以发生类型转换):
template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);first++;}}
拷贝构造:
list(const list<T>& l){//现代写法的拷贝构造empty_init();list<T> tmp(l.begin(),l.end());//先让tmp去调用区间构造创建一个带头双向双向循环链表//然后交换tmp对象中_head与*this对象中_head的值;//相当于让*this中的_head指向了tmp创建的带头双向循环链表//tmp中的_head指向*this创建的带头双向循环链表(只不过只有头而已!);swap(tmp);}
clear
功能:清除所有节点,但是不清除头节点,让整个链表的size变为0;
void clear(){iterator it = begin();while (it != end()){it=erase(it);}_head->_next = _head->_prev = _head;}
~list
析构函数与clear的功能类似,但是析构函数需要释放头节点:
~list(){clear();delete _head;_head = nullptr;}
front与back
获取头节点元素与尾节点元素
T& front(){assert(empty() == false);return _head->_next->_date;}const T& front()const{assert(empty() == false);return _head->_next->_date;}T& back(){assert(empty() == false);return _head->_prev->_date;}const T& back()const{assert(empty() == false);return _head->_prev->_date;}
insert与erase
//insert过后的pos失效,返回新插入节点的迭代器iterator insert(iterator pos, const T& val){node* prev = pos._node->_prev;node* new_node = new node(val);prev->_next = new_node;new_node->_prev = prev;new_node->_next = pos._node;pos._node->_prev = new_node;return iterator(new_node);}//erase过后pos迭代器失效!返回原pos迭代器的下一个迭代器iterator erase(iterator pos){//不能删除_headassert(empty()==false);node* prev = pos._node->_prev;node* next = pos._node->_next;delete pos._node;prev->_next = next;next->_prev = prev;pos = iterator(next);return pos;}
size与empty与swap
size_t size()const{size_t count = 0;const_iterator cit = begin();while (cit++ != end())count++;return count;}bool empty()const{return (begin() == end());}void swap(list<T>&l1){std::swap(_head,l1._head);}
pop_back()
void pop_back(){assert(empty()==false);erase(--end());}
总代码:
节点类
namespace MySpace{//节点template <class T>struct list_node{T _date;list_node<T>* _prev;list_node<T>* _next;list_node(const T& val = T()):_date(val), _prev(nullptr), _next(nullptr) {}};}
正向迭代器类
namespace MySpace
{
//利用一个类来封装list_node<T>*//让这个类作为list<T>的迭代器,然后重载这个类的++、--、*就可以实现迭代器的通用迭代办法template<class T, class Ref, class Ptr>struct _list_iterator//正向迭代器,底层封装了list_node<T>*{_list_iterator(list_node<T>* node = nullptr):_node(node) {}typedef _list_iterator<T, Ref, Ptr > self;self& operator++(){_node = _node->_next;return *this;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp = *this;--* this;return tmp;}self operator++(int){self tmp = *this;++* this;return tmp;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return !(*this!=it);}Ref operator*(){return _node->_date;}Ptr operator->(){return &_node->_date;}//成员list_node<T>* _node;};
}
反向迭代器类
namespace MySpace
{
//反向迭代器template<class Forward_iterators, class Ref, class Ptr>struct _list_reverse_iterator{_list_reverse_iterator(const Forward_iterators& it= Forward_iterators()):_it(it) {}typedef _list_reverse_iterator<Forward_iterators, Ref, Ptr> Reself;//反向迭代器类型名字太长了,换个简短点的Reself& operator++()//前置{//正向迭代器的----_it;return *this;}Reself operator++(int)//后置{Reself tmp(_it);--_it;return tmp;}Reself& operator--()//前置{//正向迭代器的--++_it;return *this;}Reself operator--(int)//后置{Reself tmp(_it);++_it;return tmp;}Ref operator*(){return *_it;}Ptr operator->(){return _it.operator->();}bool operator!=(const Reself&rit){return _it != rit._it;}bool operator==(const Reself& rit){return _it==rit;}//成员是一个正向迭代器;Forward_iterators _it;};
}
list类
namespace MySpace
{
//带头双向循环链表template<class T>class list{public:typedef _list_iterator<T, T&, T*> iterator; //这是个普通对象的正向迭代器typedef _list_iterator<T, const T&, const T*> const_iterator;//const对象的正向迭代器typedef _list_reverse_iterator<iterator, T&, T*> reverse_iterator;//普通对象反向迭代器typedef _list_reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;//const对象反向迭代器typedef list_node<T> node;//节点list(){empty_init();}list(int n, const T& val = T()){empty_init();for (int i = 0; i < n; i++){push_back(val);}}list(const list<T>& l){empty_init();list<T> tmp(l.begin(),l.end());swap(tmp);}template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);first++;}}~list(){clear();delete _head;_head = nullptr;}list<T>& operator=(list<T> l){if (this != &l){swap(l);}return *this;}void push_back(const T& val){node* tail = _head->_prev;node* new_node = new node(val);new_node->_prev = tail;tail->_next = new_node;new_node->_next = _head;_head->_prev = new_node;}void pop_back(){assert(empty()==false);erase(--end());}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(_head->_prev);}reverse_iterator rend(){return reverse_iterator(_head);}const_reverse_iterator rbegin()const{return const_reverse_iterator(_head->_prev);}const_reverse_iterator rend()const{return const_reverse_iterator(_head);}T& front(){assert(empty() == false);return _head->_next->_date;}const T& front()const{assert(empty() == false);return _head->_next->_date;}T& back(){assert(empty() == false);return _head->_prev->_date;}const T& back()const{assert(empty() == false);return _head->_prev->_date;}iterator insert(iterator pos, const T& val){node* prev = pos._node->_prev;node* new_node = new node(val);prev->_next = new_node;new_node->_prev = prev;new_node->_next = pos._node;pos._node->_prev = new_node;return iterator(new_node);}iterator erase(iterator pos){//不能删除_headassert(empty()==false);node* prev = pos._node->_prev;node* next = pos._node->_next;delete pos._node;prev->_next = next;next->_prev = prev;pos = iterator(next);return pos;}size_t size()const{size_t count = 0;const_iterator cit = begin();while (cit++ != end())count++;return count;}bool empty()const{return (begin() == end());}void swap(list<T>&l1){std::swap(_head,l1._head);}void clear(){iterator it = begin();while (it != end()){it=erase(it);}_head->_next = _head->_prev = _head;}private:list_node<T>* _head;void empty_init(){_head = new node;_head->_next = _head->_prev = _head;}};
}
相关文章:
list模拟实现
list模拟实现list原理讲解节点结构list类设计push_backIterators:begin与endconst对象的迭代器重载->运算符反向迭代器迭代器所有代码迭代器总结constructor:clear~listfront与backinsert与erasesize与empty与swappop_back()总代码:节点类正向迭代器类反向迭代器类list类lis…...
CSS看这一篇就够啦,CSS基础大全,可用于快速回顾知识,面试首选
1 CSS简介 CSS 是层叠样式表 ( Cascading Style Sheets ) 的简称。 CSS 是也是一种标记语言,主要用于设置 HTML 页面中的文本内容(字体、大小、对齐方式等)、图片的外形(宽高、边框样式、 边距等)以及版面的布局和外观…...
Canvas详细使用方法(一)
Canvas Canvas的注意事项 < canvas > 和 < img > 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。 -< canvas > 标签只有两个属性——width和height( 单位默认为px )。当没有设置宽度和高度时,canvas 会初始化宽为 300px 和高…...
CentOS定时任务——crontab
crontab Linux crontab 是用来定期执行程序的命令。 crond 命令每分钟会定期检查是否有要执行的工作,如果有要执行的工作便会自动执行该工作。 注意:新创建的 cron 任务,不会马上执行,至少要过 2 分钟后才可以,当然你…...
C51---蓝牙模块---连接软件---控制LED灯
1.器件:C51、HC-08蓝牙模块、Ty-C数据线、杜邦线 2.软件:HC蓝牙助手 3.接线:VCC-VCC、GND-GND、RXD-TXD、TXD-RXD 4.烧写:STC-ISP串口助手 5.代码: #include "reg52.h" #include "intrins.h" …...
Linux 学习笔记——二、主机规划与磁盘分区
一、Linux 与硬件的搭配 Linux 中所有设备均被视为文件,其命名规则如下: 设备文件名SCSI/SATA/USB 硬盘机/dev/sd[a-p]USB 闪存盘/dev/sd[a-p](与 SATA 相同)Virtl/O 界面/dev/vd[a-p](用于虚拟机内)软盘…...
麒麟服务器V10 版本 安装 Anaconda教程,也就是安装Python环境的教程(亲测有效)
目录1 Anaconda 是什么2 安装1 Anaconda 是什么 你可以理解为一个软件,和QQ一样的软件,你安装之后,里面就有naconda包括Conda、Python以及一大堆安装好的工具包,比如:numpy、pandas等 1)包含conda&#x…...
【3维视觉】网格细分Mesh Subdivision算法介绍(Loop, Catmull-Clark, Doo-Sabin)
引言 介绍了Loop, Catmull-Clark, Doo-Sabin细分。 算法介绍 1. Loop细分 Loop细分是Charles Loop在1987年在硕士论文中提出的一种对三角网格的细分算法。 Loop细分是递归定义的,每一个三角形一分为四,对于新生成的点和旧点以不同的规则更新。 点的…...
自学大数据第六天~HDFS命令
HDFS常用命令 查看hadoop版本 version hadoop version注意,没有 ‘-’ [hadoopmaster ~]$ hadoop version Hadoop 3.3.4 Source code repository https://github.com/apache/hadoop.git -r a585a73c3e02ac62350c136643a5e7f6095a3dbb Compiled by stevel on 2022-07-29T12:3…...
maven仓库的配置
下载 官网下载:https://maven.apache.org/download.cgi 2. 配置maven环境 右键电脑 ->属性 -> 高级系统设置 -> 环境变量 -> 系统变量-新建 变量名:MAVEN_HOME 变量值为maven的文件安装地址 编辑Path系统变量 新建:%MAVE…...
医院信息管理云平台源码 云HIS系统源码 4级电子病历系统
基层医院云HIS系统源码 高端商业his源码 有演示,可直接项目运营。 一款满足基层医院各类业务需要的云HIS系统。该系统能帮助基层医院完成日常各类业务,提供病患挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生站和护士站等一系列常规…...
JS学习第9天——ES6中面向对象(类class、constructor构造函数、类的继承extends、super关键字、面向对象tab栏切换案例)
目录一、面向对象1、面向过程2、面向对象3、两者对比二、ES6中的类和对象1、面向对象的思维特点2、对象3、类class4、类constructor构造函数三、类的继承1、继承2、super()关键字3、注意点四、面向对象案例一、面向对象 两大编程思想:① 面向过程 ② 面向对象 1、…...
K8S核心秘术学习总纲
学习 Kubernetes (简称 K8S) 可以采取以下步骤: 了解 K8S 的基本知识:K8S 是一个负责管理容器的开源平台。 在学习 K8S 之前,需要先掌握 Linux 基础知识和 Docker 容器基础知识。 搭建 K8S 环境:为了学习 K8S,你需要有…...
【PTA-训练day27】L2-038 病毒溯源 + L2-039 清点代码库 + L2-040 哲哲打游戏
目录 L2-038 病毒溯源 - dfs求树最大深度及路径 L2-039 清点代码库 - STL嵌套使用结构体自定义排序 L2-040 哲哲打游戏 - vector建图 L2-038 病毒溯源 - dfs求树最大深度及路径 PTA | 程序设计类实验辅助教学平台 思路: 用链表建树 找到根节点dfs根节点寻找最大…...
新一代跨平台云备份工具Duplicacy
什么是 Duplicacy ? Duplicacy 是一款云备份软件,通过 Duplicacy 可以将视频,图片,文件,注册表等数据备份到云端。Duplicacy 通过客户端加密和最高级别的重复数据删除功能,将您的文件备份到许多云存储。 安…...
考研复试——概率论
文章目录概率论1. 大数定律2. 中心极限定理3. 大数定律和中心极限定理的区别?4. 最大似然估计5. 古典概型6. 几何概型7. 全概率公式8. 贝叶斯公式9. 先验概率、后验概率10. 数学期望因为初试考的数二,没有学概率论,要从头学习时间也不够&…...
Web学习4_JavaScript常用库
常用库 jQuery 使用方式 在元素中添加: <script src"https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"> </script> 按jQuery官网提示下载 选择器 $(selector),例如: $(div);$(.big-div); $(div > p)s…...
C++回顾(二十)—— vector容器 和 deque容器
20.1 vector容器 20.1.1 vector容器简介 vector是将元素置于一个动态数组中加以管理的容器。vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法)。vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比…...
httpd使用记录
httpd使用记录 Busybox用一个httpd的程序,尝试用起来。 简单测试 启动服务 # 启动服务 mkdir /var/www/html httpd -p 8080 -h /var/www/html &编写html文件 在/var/www/html下放一个测试网页index.html文件。 <!DOCTYPE html> <html><hea…...
.vue 组件打包成 .js
.vue 组件打包成 .js *** 所有的内容 cli 官网都有 *** *** https://cli.vuejs.org/zh/guide/build-targets.html *** 所有的内容 cli 官网都有: https://cli.vuejs.org/zh/guide/build-targets.html 准备 几个 .vue 组件文件 import Main from ./components/Ma…...
Java 代码分享(第11篇)编程解决数学问题:“计算3个10以内的数字,与合计值相除后,商的第3位小数大于4,共有多少个数的组合满足条件”类似问题
求与合计相除,小数位大于4的数字组合 1 3 4 9 17 1 / 17 ≈ 0.05882 3 / 17 ≈ 0.17647 4 / 17 ≈ 0.23529 9 / 17 ≈ 0.52941 可以发现,每一个商的第三位都是大于等于5的数,四舍五入后会进位。 下面的程序可以生成符合这样条件的数据。…...
面试题 17.05. 字母与数字
题目链接 面试题 17.05. 字母与数字 mid 题目描述 给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。 返回该子数组,若存在多个最长子数组,返回左端点下标值最小的子数组。若不存在这样的数组&…...
解决Win10图片/文件右键单击自动退出并刷新桌面问题
问题描述 这两天开始不知道怎么回事儿,右键选择图片时候,电脑黑屏且资源管理器自动重启。然后我就开始找很多方法去解决。 我试了很多种复杂的简单的方法,但是只有一种解决了我的问题。 解决方案【解决我的问题】 这个方法如下࿱…...
【代码随想录训练营】【Day39】第九章|动态规划|62.不同路径|63. 不同路径 II
不同路径 题目详细:LeetCode.62 有点简单呀,做类似这种题型时,最好就是先画图: 可以像题目一样,画一个二维表格,表格内的值代表到达这个格子的不同路径总数那么已知,如果图的大小为m 1 || n…...
【Linux】linux | 修改系统编码 | 增加字体处理 | 图片处理字体变成方块
一、说明1、CentOS7二、修改系统编码编辑文件vi /etc/locale.conf修改编码并保存LANGzh_CN.UTF-8配置生效source /etc/locale.conf1)修改系统编码,只是让系统支持中文编码2)不解决文字不显示的问题;往后看三、解决字体不显示问题非…...
R语言介绍及安装教程
R语言是一种免费的开源编程语言和环境,主要用于数据分析、统计建模和可视化。它可以运行在不同的操作系统上,如Windows、MacOS和Linux。R语言具有以下特点:丰富的数据处理和统计分析函数库;易于学习和使用;可以生成高质…...
Linux 练习九 (IPC 消息队列)
文章目录消息队列有亲缘关系的进程使用消息队列通信无亲缘关系的进程使用消息队列通信使用环境:Ubuntu18.04 使用工具:VMWare workstations ,xshell作者在学习Linux的过程中对常用的命令进行记录,通过思维导图的方式梳理知识点&am…...
在Win 11下使用Visual Studio 2019和cygwin编译JBR(Java SDK 17)源码
很多文章介绍了JDK 8和JDK11源码在Linux编译,很少有人介绍了JDK 17在windows的编译过程,所以写了这篇文章,为什么选用JBR 17版本,因为JBR17 版本集成了HotSwapAgent功能,具体HotSwapAgent有什么用,请看我前…...
java基础学习 day51 (匿名内部类)
1. 什么是匿名内部类? 隐藏了名字的内部类,实际名字为:外部类名$序号可以写在成员位置,为没有名字的成员内部类也可以写在局部位置,为没有名字的局部内部类 2. 匿名内部类的格式? new 类名/接口名() { 重…...
Spring MVC程序开发(三大功能)
文章目录一、什么是Spring MVC?1.MVC定义2.MVC与Spring MVC的关系3.创建方式二、Spring MVC的核心功能1.连接功能浏览器获取前端接口和后端程序连接功能实现get和post的区别Spring Boot热部署2.获取参数(1)传递单个参数(2)传递对…...
电商专业培训网站建设/网站搜索
一、帖子列表快速发帖自动排版1、修改 /template/default/forum/下的forumdisplay_fastpost.htm (注意备份)查找: <a href"forum.php?modpost&actionnewthread&fid$_G[fid]" οnclick"switchAdvanceMode(this.h…...
法院 公开网站建设情况/怎么网站推广
(Uva 6955可以直接随机,湖大OJ 13348 要优化) 题意:给出 n个点的坐标, 一个 百分数p, 求是否有一条直线上有 n * p /100个点… 随机化算法,但也要优化下……(TLE, WA到底…...
门头沟网站建设公司/万能bt搜索引擎网站
流媒体指的是在网络中使用流技术传输的连续时基媒体,其特点是在播放前不需要下载整个文件,而是采用边下载边播放的方式,它是视频会议、IP电话等应用场合的技术基础。RTP是进行实时流媒体传输的标准协议和关键技术,本文介绍如何在L…...
外贸网站推广怎样做/seo优化的优点
kafka学习笔记 kafka系列四、kafka架构原理、高可靠性存储分析及配置优化 kafka系列八、kafka消息重复和丢失的场景及解决方案分析 kafka消息的分发与消费与高级应用 springboot下 kafka 手动创建topic并指定分区(partition)数及分区副本(replica)数 Topic&Partition 的…...
wordpress 中国/最有效的恶意点击软件
1 class A 2 {3 private:4 int a, b;5 public:6 //A ob1; //这个地方会产生个error:不能使用正在定义的“A”类定义对象7 A* ob; //相比而言,正在定义的类可以用来声明对象的指针变量,8 9 A() //默认构造函数…...
企业网站建站 广州 视频/爱站网挖掘关键词
PDF签名仓库介绍介绍使用签名其他仓库介绍 仓库地址 该工具从签名照片中提取文字,并签名到PDF文档的指定位置。除此之外,该仓库还支持其他PDF操作,如合并、OCR、水印功能等。 介绍 该工具接受PDF文件和签名图片作为输入,并输出…...