行业知识
C++ stl容器list的底层模拟实现
目录
前言:
1.创建节点
2.普通迭代器的封装
3.反向迭代器的封装
为什么要对正向迭代器进行封装?
4.const迭代器
5.构造函数
6.拷贝构造
7.赋值重载
8.insert
9.erase
10.析构
11.头插头删,尾插尾删
12.完整代码+简单测试
总结:
前言:
模拟实现list,本篇的重点就是由于list是一个双向循环链表结构,所以我们对迭代器的实现不能是简单的指针的++,--了,因为我们知道,链表的存储不一定是连续的,所以直接++,--是链接不起来节点的,所以我们要对迭代器也就是对节点的指针进行封装。结尾会附上完整的代码。
1.创建节点
templatestruct list_node{list_node* _prev;list_node* _next;T _data;list_node(const T& x= T())//这里不给缺省值可能会因为没有默认构造函数而编不过:_prev(nullptr),_next(nullptr),_data(x){}}; 注意给缺省值,这样全缺省就会被当做默认构造了,不会因为没有默认构造而报错。
我们实现的list是带哨兵位的,它同时是迭代器的end()(因为是双向循环的list)。
2.普通迭代器的封装
templatestruct _list_iterator{typedef list_node node;typedef _list_iterator self;node* _node;//对迭代器也就是节点的指针进行封装,因为list迭代器是不能直接++的_list_iterator(node* n):_node(n){}Ref operator*()//返回的必须是引用,不然改变不了外面的对象的成员,要支持对自己解引用改变值就要用应用{return _node->_data;}Ptr operator->(){return &(_node->_data);//返回地址,再解引用直接访问数据}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& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}}; 注意list是双向迭代器,可以++,--,不能+,-
这里对迭代器的实现就如我们开始所说的, 迭代器的实现就是使用节点的指针实现的,而我们不能直接对list创建出的节点进行++,--,所以要进行一层封装;然后再对节点指针初始化。
重载解引用时要注意返回的是引用,不然对自己解引用的时候,返回值如果是临时的,是改变不了内部的data的。
对于箭头的解引用,是为了支持这样的场景:
struct AA{int _a1;int _a2;AA(int a1=0,int a2=0):_a1(a1),_a2(a2){}};void test_list2(){list lt;lt.push_back(AA(1,1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));list::iterator it = lt.begin();while (it != lt.end()){//cout _prev = _head;}list(){empty_Init();}templatelist(Iterator first, Iterator end){empty_Init();//别忘加上哨兵位,没有哨兵位识别不了endwhile (first != end){push_back(*first);first++;//这里的++first会调用重载的,因为传过来的是一个迭代器}} 哨兵位是空的,不放数据,但是哨兵位是正向迭代器的end,要加上。
默认无参构造就只有哨兵位,提供的迭代器的构造也要有哨兵位。
first++不用担心,first是迭代器类型的,所以会调用迭代器的++。
6.拷贝构造
//传统的拷贝构造//list(const list& lt)//{//empty_Init();//for (auto e : lt)//{//push_back(e);//this->push_back(e)//}//}void swap(list& tmp)//要使用库中的swap,而库中的swap就不带const;况且交换的是头节点,const修饰的就不能修改指向{std::swap(_head, tmp._head);}//现代的拷贝构造list(const list& lt){empty_Init();list tmp(lt.begin(), lt.end());//为什么还要多一个变量,因为下面swap的参数没有const,而拷贝构造要加constswap(tmp);//this->swap(tmp)} 拷贝构造,直接使用库中的swap,交换头节点也就是哨兵位的指向就行,因为链表后面的关系都通过头节点找到,所以也就相当于都交换了。
注意库中swap的参数:
7.赋值重载
list& operator=(list lt)//参数不能使用引用,使用引用再使用swap交换,原来赋值的值就被改了{swap(lt);return *this;} 一样是使用库中的swap,但是赋值的参数不能是引用,例如L1=L3,用引用再加上使用swap交换头节点的指向,L3就被改了,我们要求的是赋值是不能改变赋过来的对象的,内置类型也是(a=b)。
8.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->_prev = prev;newnode->_next = cur;cur->_prev = newnode;} 链接节点即可,注意插入的值可能是任意类型,所以要用模版参数并且带上const与引用,防止是内置类型的值是const,传过来权限放大。
插入pos位置,也就是在pos前和pos位置之间插入。
9.erase
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 pos._node;return iterator(next);} 注意删除完返回删除数据的下一个迭代器位置。
删除就是找前找后,删除节点,链接前后。
_node是new出来的,注意配套使用。
10.析构
void clear(){iterator it = begin();while (it != end()){it= erase(it);//删除后返回的是下一个数据的位置,所以循环就走起来了}}~list(){clear();delete _head;_head = nullptr;} 注意迭代器的erase删除后返回的是删除数据的下一个迭代器位置,所以用it接收就不怕迭代器失效了,同时循环也走起来了。
11.头插头删,尾插尾删
void push_back(const T& x){/*node* tail = _head->_prev;node* newnode = new node(x);tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;*/insert(end(), x);}void push_front(const T& x){insert(begin(),x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());} 直接复用即可。
12.完整代码+简单测试
封装的反向迭代器:
#pragma oncenamespace my_iterator{templatestruct ReverseIterator{typedef ReverseIterator self;Iterator _cur;ReverseIterator(Iterator it):_cur(it){}Ref operator*(){Iterator tmp = _cur;//因为要--,而解引用是不能改值的,所以用tmp改并返回--tmp;return *tmp;}Ptr operator->(){return &operator*();}self operator++(){--_cur;//直接的++--就能直接改了,所以可以直接返回原对象return *this;}self operator--(){++_cur;return *this;}bool operator!=(const self& s){return _cur != s._cur;}};} #pragma once#include "my_iterator.h"#include #include #include using namespace my_iterator;using namespace std;namespace my_list{templatestruct list_node{list_node* _prev;list_node* _next;T _data;list_node(const T& x= T())//这里不给缺省值可能会因为没有默认构造函数而编不过:_prev(nullptr),_next(nullptr),_data(x){}};templatestruct _list_iterator{typedef list_node node;typedef _list_iterator self;node* _node;//对迭代器也就是节点的指针进行封装,因为list迭代器是不能直接++的_list_iterator(node* n):_node(n){}Ref operator*()//返回的必须是引用,不然改变不了外面的对象的成员,要支持对自己解引用改变值就要用应用{return _node->_data;}Ptr operator->(){return &(_node->_data);//返回地址,再解引用直接访问数据}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& s){return _node != s._node;}bool operator==(const self& s){return _node == s._node;}};templateclass list{typedef list_node node;public:typedef _list_iterator iterator;typedef _list_iterator const_iterator;typedef ReverseIterator reverse_iterator;typedef ReverseIterator const_reverse_iterator;void empty_Init(){_head = new node;_head->_next = _head;_head->_prev = _head;}list(){empty_Init();}templatelist(Iterator first, Iterator end){empty_Init();//别忘加上哨兵位,没有哨兵位识别不了endwhile (first != end){push_back(*first);first++;//这里的++first会调用重载的,因为传过来的是一个迭代器}}//传统的拷贝构造//list(const list& lt)//{//empty_Init();//for (auto e : lt)//{//push_back(e);//this->push_back//}//}void swap(list& tmp)//要使用库中的swap,而库中的swap就不带const;况且交换的是头节点,const修饰的就不能修改指向{std::swap(_head, tmp._head);}//现代的拷贝构造list(const list& lt){empty_Init();list tmp(lt.begin(), lt.end());//为什么还要多一个变量,因为下面swap的参数没有const,而拷贝构造要加constswap(tmp);//this->swap(tmp)}list& operator=(list lt)//参数不能使用引用,使用引用再使用swap交换,原来赋值的值就被改了{swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it= erase(it);//删除后返回的是下一个数据的位置,所以循环就走起来了}}~list(){clear();delete _head;_head = nullptr;}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);//哨兵位就是end}const_iterator begin() const//本身const迭代器是让迭代器指向的内容不能修改,但是这样用const修饰迭代器本身也不能修改了{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}void push_back(const T& x){/*node* tail = _head->_prev;node* newnode = new node(x);tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;*/insert(end(), x);}void push_front(const T& x){insert(begin(),x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos,const T& x){node* cur = pos._node;node* prev = cur->_prev;node* newnode = new node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = 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 pos._node;return iterator(next);}private:node* _head;};void print_list(const list& lt){list::const_iterator it = lt.begin();//不能直接这样写,传递过来的this指针也是const list*,权限放大了,要提供const版本while (it != lt.end()){cout