利用C++模拟实现STL容器:list
蒋灵瑜的笔记本 人气:0一、list的介绍
列表是一种顺序容器,它允许在序列中的任何位置执行常量时间插入和删除操作,并允许在两个方向上进行迭代。
它的底层是一个带头双向循环链表。附一篇博主用C语言写的带头双向循环链表:【数据结构】带头双向循环链表的实现
二、list的排序
list不能用算法库的sort进行排序。算法库中的sort的底层是一个快排,需满足三数取中,需要传入随机访问迭代器,所以list并不适用。
当然list中提供了一个自己的sort,它的底层是一个归并排序。但是这个sort比vector使用算法库的sort还慢,甚至比list的数据先push_back到vector到再用算法库的sort还要慢。
三、迭代器
1、list的迭代器失效问题
insert,迭代器不失效
earse失效
2、迭代器的功能分类
1、单向迭代器:只能++,不能--。例如单链表,哈希表;
2、双向迭代器:既能++也能--。例如双向链表;
3、随机访问迭代器:能++--,也能+和-。例如vector和string。
迭代器是内嵌类型(内部类或定义在类里)
3、list迭代器的模拟实现
普通迭代器
迭代器的实现需要支持解引用(为了取数据),支持++--。
博主模拟实现string和vector时,直接将原生指针typedef成迭代器,是因为数组的结构正好满足迭代器的行为(注:string和vector可以用原生指针实现,但是vs中采用自定义类型封装的方式实现),但是list中的节点地址是不连续的,不能使用原生指针,需要使用类进行封装+运算符重载实现。
//用类封装迭代器 template <class T> struct __list_iterator { typedef list_node<T> node; //用节点的指针进行构造 __list_iterator(node* p) :_pnode(p) {} //迭代器运算符的重载 T& operator*() { return _pnode->_data; } __list_iterator<T>& operator++()//返回值不要写成node* operator++(),因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对 { //return _pnode->_next; _pnode=_pnode->_next; return *this;//返回的是迭代器 } bool operator!=(const __list_iterator<T>& it) { return _pnode != it._pnode; } public: node* _pnode;//封装一个节点的指针 };
const迭代器
const迭代器的错误写法:
typedef __list_iterator<T> iterator; const list<T>::iterator it=lt.begin();
因为typedef后,const修饰的是迭代器it,只能调用operator*(),调不了operator++()。(重载operator++()为const operator++()也不行,因为const版本++还是改变不了)
正确写法:想实现const迭代器,不能在同一个类里面动脑筋,需要再写一个const版本迭代器的类。
//用类封装const迭代器 template <class T> struct __list_const_iterator { typedef list_node<T> node; //用节点的指针进行构造 __list_const_iterator(node* p) :_pnode(p) {} //迭代器运算符的重载 const T& operator*()const { return _pnode->_data; } __list_const_iterator<T>& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对 { //return _pnode->_next;//返回类型错误的 _pnode = _pnode->_next; return *this;//返回的是迭代器 } __list_const_iterator<T>& operator--() { _pnode = _pnode->_prev; return *this; } bool operator!=(const __list_const_iterator<T>& it)const { return _pnode != it._pnode; } public: node* _pnode;//封装一个节点的指针 }; typedef __list_const_iterator<T> const_iterator;
当然,这样写__list_iterator和__list_const_iterator这两个类会出现代码重复。STL库中是通过类模板多给一个参数来实现,这样,同一份类模板就可以生成两种不同的类型的迭代器(以下为仿STL库的模拟实现):
//用类封装普通/const迭代器 template <class T,class Ref> struct __list_iterator { typedef list_node<T> node; typedef __list_iterator<T,Ref> Self; //用节点的指针进行构造 __list_iterator(node* p) :_pnode(p) {} //迭代器运算符的重载 Ref operator*() { return _pnode->_data; } Self& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对 { //return _pnode->_next;//返回类型错误的 _pnode=_pnode->_next; return *this;//返回的是迭代器 } Self& operator--() { _pnode = _pnode->_prev; return *this; } bool operator!=(const Self& it) { return _pnode != it._pnode; } public: node* _pnode;//封装一个节点的指针 }; typedef __list_iterator<T, T&> iterator; typedef __list_iterator<T, const T&> const_iterator;
4、迭代器价值
1、封装底层实现,不暴露底层实现的细节;
2、多种容器提供统一的访问方式,降低使用成本;
C语言没有运算符重载和引用等语法,是实现不了迭代器的。
5、迭代器operator->的重载
迭代器的用法就是模拟指针的行为,如果现在有一个指向结构的指针,那么就需要用到->来解引用。
//*的重载:返回节点的数据 Ref operator*() { return _pnode->_data; } //->的重载:返回数据的指针 T* operator->() { return &_pnode->_data; }
但是operator->使用T*做返回值类型,这样无论是普通迭代器和const迭代器都能修改,所以operator->的返回值类型应该改为泛型:
template <class T, class Ref,class Ptr> Ptr operator->() { return &_pnode->_data; } typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator;
四、模拟实现时遇到的困惑及注意点
1、调用拷贝构造时,链表内节点数据为什么已经是深拷贝了?
2、类名和类型的区别
普通类:类名等于类型
类模板:类名不等价于类型,例如list类模板类名是list,类型list<int>等。
所以我们平常写函数形参和返回值时,总会带上形参和返回值的类型:
// 赋值运算符重载写法2(赋值运算符重载都可以这么干) list<T>& operator=(list<T> lt) { swap(lt); return *this; }
但是C++在类模板里面可以用类名代替类型:
// 赋值运算符重载写法2(赋值运算符重载都可以这么干) list& operator=(list lt) { swap(lt); return *this; }
建议写代码的时候严格区分类型和类名,让自己和别人能够看的很明白。(了解下C++有这种坑语法即可)
五、vector和list的优缺点
vector和list像左右手一样,是互补关系,缺一不可。vector的优点正是list的缺点,list的优点也是vector的缺点,实际选用容器时,按照需求择优选用。
1、vector
vector的优点(结构厉害):
1、支持下标的随机访问;
2、尾插尾删效率高(当然扩容的那一次尾插尾删会较慢);
3、CPU高速缓存命中高(数据从缓存加载至CPU中,会加载连续的一段数据,vector因为结构连续,高速缓存命中高)。
vector的缺点:
1、非尾插尾删效率低;
2、扩容有消耗,并存在一定的空间浪费。
vector迭代器失效问题:
insert/erase均失效。(如果string的insert和erase形参是迭代器,那么也会失效,但是大部分接口是下标传参,不考虑失效问题,只有几个接口是迭代器传参,需要注意迭代器失效问题)
2、list
list的优点:
1、按需申请释放,无需扩容;
2、任意位置插入删除时间O(1);(这里说的是插入删除,不要加上查找的时间)
list的缺点:
1、不支持下标的随机访问;
2、CPU高速缓存命中率低;
3、每一个节点除了存储数据外,还需要额外存储两个指针。
list迭代器失效问题:
insert不失效,erase失效。
六、模拟实现list整体代码
#pragma once #include <iostream> #include <algorithm> #include <assert.h> #include <vector> using std::cout; using std::endl; namespace jly { //链表节点的类 template <class T> struct list_node { list_node(const T& x = T())//给一个缺省值,变成默认构造函数 :_next(nullptr) , _prev(nullptr) , _data(x) {} list_node* _next; list_node* _prev; T _data; }; //用类封装普通/const迭代器 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* p) :_pnode(p) {} //迭代器运算符的重载 Ref operator*() { return _pnode->_data; } Ptr operator->() { return &_pnode->_data; } Self& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对 { //return _pnode->_next;//返回类型错误的 _pnode = _pnode->_next; return *this;//返回的是迭代器 } Self operator++(int)//后置++ { _pnode = _pnode->_next; return Self(_pnode->_next); } Self& operator--() { _pnode = _pnode->_prev; return *this; } Self operator--(int)//后置-- { _pnode = _pnode->_prev; return Self(_pnode->_prev); } bool operator!=(const Self& it)const { return _pnode != it._pnode; } bool operator==(const Self& it)const { return _pnode == it._pnode; } public: node* _pnode;//封装一个节点的指针 }; //用类封装const迭代器 //template <class T> //struct __list_const_iterator //{ // typedef list_node<T> node; // //用节点的指针进行构造 // __list_const_iterator(node* p) // :_pnode(p) // {} // //迭代器运算符的重载 // const T& operator*()const // { // return _pnode->_data; // } // __list_const_iterator<T>& operator++()//返回值不要写成node*,因为迭代器++肯定返回迭代器啊,你返回节点指针类型不对 // { // //return _pnode->_next;//返回类型错误的 // _pnode = _pnode->_next; // return *this;//返回的是迭代器 // } // __list_const_iterator<T>& operator--() // { // _pnode = _pnode->_prev; // return *this; // } // bool operator!=(const __list_const_iterator<T>& it)const // { // return _pnode != it._pnode; // } //public: // node* _pnode;//封装一个节点的指针 //}; //链表类(控制哨兵位) 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; //typedef __list_const_iterator<T> const_iterator; //构造函数 void empty_initialize()//用于初始化哨兵位 { _head = new node; _head->_next = _head; _head->_prev = _head; _size = 0; } list() { empty_initialize(); } template <class InputIterator> list(InputIterator first, InputIterator last)//迭代器区间构造 { empty_initialize(); while (first != last) { push_back(*first); ++first; } } //析构函数 ~list() { clear(); delete _head; _head = nullptr; } //拷贝构造 list(const list<T>& lt) { 先初始化*this //empty_initialize(); //for (const auto& e : lt)//取lt的数据给e //{ // push_back(e); //} //工具人写法 list<T> tmp(lt.begin(),lt.end()); empty_initialize(); swap(tmp); } void swap(list<T>& lt) { std::swap(_head, lt._head);//交换头指针 std::swap(_size,lt._size); } 赋值运算符重载写法1 //list<T>& operator=(const list<T>& lt) //{ // //防止自己给自己赋值 // if (this != <) // { // clear(); // for (const auto& e : lt) // { // push_back(e); // } // } // return *this; //} // 赋值运算符重载写法2(赋值运算符重载都可以这么干) list<T>& operator=(list<T> lt) { swap(lt);//直接交换 return *this; } //插入删除 void push_back(const T& x) { /*node* newNode = new node(x); node* tail = _head->_prev; newNode->_prev = tail; newNode->_next = _head; tail->_next = newNode; _head->_prev = newNode;*/ insert(end(), x); } void pop_back() { erase(--end()); } void push_front(const T& x)//头插 { insert(begin(), x); } void pop_front() { erase(begin()); } iterator insert(iterator pos, const T& x) { node* newNode = new node(x); node* prev = pos._pnode->_prev; node* cur = pos._pnode; newNode->_prev = prev; newNode->_next = cur; prev->_next = newNode; cur->_prev = newNode; //return iterator(newNode); pos._pnode = newNode; ++_size; return pos; } iterator erase(iterator pos) { assert(!empty()); node* prev = pos._pnode->_prev; node* next = pos._pnode->_next; prev->_next = next; next->_prev = prev; delete pos._pnode; --_size; //return iterator(next); pos._pnode = next; return pos; } //链表小接口 bool empty()const { return _head->_next == _head; } void clear() { /*assert(!empty); node* cur = _head->_next; while (cur != _head) { node* next = cur->_next; delete cur; cur = next; }*/ iterator it = begin(); while (it != end()) { it = erase(it);//erase返回删除元素的下一个 } } size_t size()const { return _size; } //普通begin(),end()迭代器 iterator begin() { //return iterator(_head->_next); return __list_iterator<T, T&,T*>(_head->_next); } iterator end() { return __list_iterator<T, T&,T*>(_head); } //const begin(),end()迭代器 const_iterator begin()const { //return const_iterator(_head->_next); return __list_iterator<T, const T&,const T*>(_head->_next); } const_iterator end()const { return __list_iterator<T, const T&,const T*>(_head); } private: node* _head;//哨兵位 size_t _size;//用于统计节点个数,空间换时间 //不加这个私有变量,统计节点个数时间O(N),有这个私有变量,时间O(1),但是每个节点的体积变大 }; //测试函数 struct Pos { int _row; int _col; Pos(int row = 0, int col = 0) :_row(row) , _col(col) {} }; void test() { list<Pos> i; i.push_back(Pos(1, 2)); i.push_back(Pos(2, 5)); i.push_back(Pos(4, 3)); list<Pos>::iterator it = i.begin(); while (it != i.end()) { cout << (&( * it))->_row;//*it取数据,再取地址、解引用得到_row,多此一举 cout << it->_row;//同第三种写法,编译器为了可读性,省略了一个-> cout << it.operator->()->_row;//it.operator->()是显示调用,->_row是解引用得到_row it++; } } void test1() { list<std::vector<int>> i; std::vector<int> v1(1, 2); std::vector<int> v2(2, 4); std::vector<int> v3(3, 5); i.push_back(v1); i.push_back(v2); i.push_back(v3); list<std::vector<int>> m(i); i = m; cout << m.size(); } }
加载全部内容