C++ STL:从零开始模拟实现 list 容器
文章目录
- 引言
- 1. 疑难点解析
- 1.1 迭代器类为什么设置三个模版参数?
- 2. 完整源码
- 3. 完整测试代码
引言
C++ 标准模板库(STL)中的 list 是一个双向链表容器,它提供了高效的插入和删除操作。本文将带领你一步步实现一个简化版的 list 容器,帮助你深入理解其底层原理和设计思想。
1. 疑难点解析
1.1 迭代器类为什么设置三个模版参数?
为了区分 list 的 const类 和 普通类 ,我们都知道 const类 的变量是不能修改内部值的,就算用指针也不行。而我们的迭代器也正好是在模仿指针的行为,所以指针不能修改 const类 变量,我们的迭代器也不行。
为了区分对 const类 的 list(只读) 和 普通类list(可读可写)的区别,我们需要另一个能做到对容器只读不写的 const类迭代器 ,我们最好的方式便是写两个基本一样的迭代器类(一份供普通类list调用的普通迭代器类,一份供const类list调用的const类的迭代器)。
但这样既不好修改又不好调试,而在有了模版之后,我们便可以将写两份代码的工作交给编译器去实现(俗称模版实例化,根据传入参数不同,模版可以实例化为多个不同类),所以我们就多加了一个模版参数Ref,根据 list 传入的类型不同,从而区分实例化为 const类 的迭代器还是 普通类 ,而又因为我们迭代器可以用 * 和 -> 两种操作符访问,且两操作符的返回值又各不同,所以 迭代器模版 又多加一个 Ptr ,以区分 ->运算符重载函数 对 const类 和 普通类 的访问。
2. 完整源码
#include<assert.h>namespace dza {// 惯例:当成员变量和成员函数全部是公有的时候,一般用struct// struct 与 class没有区别template<class T>struct list_node {T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}};// 单一参数的模板//template<class T>//struct list_iterator {// typedef list_node<T> Node;// // 定义一个迭代器Self// typedef list_iterator<T> Self;// Node* _node;// list_iterator(Node* node)// :_node(node)// {}// // 解引用的重载// T& operator*()// {// return _node->_data;// }// // 公共函数。域内都可以使用// T* operator->()// {// return &_node->_data;// }// // 迭代器的实现// Self& operator++()// {// _node = _node->_next;// return *this;// }// Self& operator--()// {// _node = _node->_prev;// return *this;// }// Self operator++(int)// {// Self tmp(*this);// _node = _node->_next;// return tmp;// }// 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;// }//};// 模板里面的多参数template<class T,class Ref,class Ptr>struct list_iterator {typedef list_node<T> Node;// 定义一个迭代器Selftypedef list_iterator<T,Ref,Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}// 解引用的重载Ref operator*(){return _node->_data;}// 公共函数。域内都可以使用Ptr operator->(){return &_node->_data;}// 迭代器的实现Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}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;}};template<class T>class list {typedef list_node<T> Node;public://typedef list_iterator<T> iterator;// typedef list_const_iterator<T> iterator;// 这个类要你自己实现// or// list_iterator要有三个模板参数,才能用下面的迭代器,并且const是加在后面的,而不是前面typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;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);}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}// it2(it1) 的模拟实现list(const list<T>& It){empty_init();for (auto& e : It){push_back(e);}}// it2 = it3 的模拟实现list<T>& operator=(list<T> It){swap(It);return *this;}~list(){clear();delete _head;_head = nullptr;}void swap(list<T>& tmp){std::swap(_head, tmp._head);std::swap(_size, tmp._size);}void clear(){auto it = begin();while (it != end()){it = erase(it);// 删除链表中的所有节点// erase在删除的时候会自动+1}}// it1(3,1)的模拟实现。往list中插入3个1list(size_t n, const T& val = T()){// 清空新链表empty_init();// 依次插入for (size_t i = 0; i < n; i++){push_back(val);}}void push_back(const T& x){Node* new_node = new Node(x);// 指向待插入数据的指针Node* tail = _head->_prev;// 头结点的上一个节点。也就是链表的最后一个节点tail->_next = new_node;// 最后一个节点的 next 指向 new_nodenew_node->_prev = tail;// new_node 的 prev 指向最后一个节点new_node->_next = _head;// 由于 new_node 成为最后一个节点。所以更新链表中头结点的下一个指针_head->_prev = new_node;// 头节点的 prev 指向 new_node// // 新版本// insert(end(),x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos, const T& val){// cur 需要插入新数据的节点Node* cur = pos._node;// newnode 需要插入的新数据Node* newnode = new Node(val);// 需要插入新数据节点的上一个节点Node* prev = cur->_prev;// list是链表,所以想要头插,就只需要改变上一个节点的next,下一个节点的prevprev->_next = newnode;// 上一个节点的 next 换成 newnodenewnode->_prev = prev;// 新数据指向上一个节点的指针 prev 指向上一个节点 newnode->_next = cur;// 新数据的下一个节点指向节点的原数据cur->_prev = newnode;// 原节点的 prev 指向新数据++_size;// 改变整个链表的长度// return iterator 通常表示一个函数返回一个 迭代器(iterator) 对象// 它指向某个容器(如 std::vector、std::list、std::map 等)中的特定元素。return iterator(newnode);// 返回新数据}iterator erase(iterator pos){// 先判断是不是空链表assert(pos != end());Node* del = pos._node;// 指向待删除数据的节点的指针Node* next = del->_next;// 指向待删除数据节点的下一个节点Node* prev = del->_prev;// 指向待删除数据节点的上一个节点prev->_next = next;// 上一个节点的 next 指针指向下一个节点next->_prev = prev;// 下一个节点的 prev 指针指向上一个节点delete del;// 删除当前的节点--_size;// 减小整个数组的size// 返回待删除节点的下一个节点return iterator(next);}private:Node* _head;size_t _size;};// 通用的 swap 模版。所有的类型都能用template <class T>void swap(T& a, T& b){T c(a); a = b; b = c;}// 特化的 list_swap 版本。仅适用于 list<T>类型 使用// 特化的模版函数可以提高程序的运行效率template<class T>void swap(list<T>& a, list<T>& b){a.swap(b);}
}
3. 完整测试代码
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;
#include<list>
#include<vector>
#include<algorithm>#include"list.h"
//#include"teacher's list.h"class Pos
{
public:int _row;int _col;Pos(int row = 0, int col = 0):_row(row), _col(col){cout << "Pos(int row, int col)" << endl;}Pos(const Pos& p):_row(p._row), _col(p._col){cout << "Pos(const Pos& p)" << endl;}
};void test_list01()
{dza::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);for (auto& e : lt1){cout << e << " ";}cout << endl;auto it1 = lt1.begin();while (it1 != lt1.end()){cout << *it1++ << " ";}cout << endl;
}void test_list02()
{dza::list<int> lt1;lt1.push_back(1);lt1.push_back(1);lt1.push_back(1);lt1.push_back(1);dza::list<int>::iterator it1 = lt1.begin();while (it1 != lt1.end()){*it1 = 2;cout << *it1 << " ";++it1;}cout << endl;for (auto e : lt1){cout << e << " ";}cout << endl;
}void test_list03()
{dza::list<Pos> lt2;// _row构造次数 == 0,_row构造次数 r1++// _col构造次数 == 0,_col构造次数 c1++Pos p1(1, 1);lt2.push_back(p1);// r1++,c1++lt2.push_back(Pos(2, 2)); // r1++,c1++// {}隐式类型转换lt2.push_back({ 3,3 });// r1++,c1++// 动用4次_row构造。4次_col构造dza::list<Pos>::iterator it2 = lt2.begin();while (it2 != lt2.end()){//cout << (*it2)._row << ":" << (*it2)._col << endl;// 为了可读性,特殊处理,省略了一个->// 两种读取方式。打印两次cout << it2->_row << ":" << it2->_col << endl;cout << it2.operator->()->_row << ":" << it2.operator->()->_col << endl;++it2;}cout << endl;
}void test_list04()
{dza::list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);for (auto& e : l1){cout << e << " ";}cout << endl;auto it1 = l1.begin();int x = 0;l1.pop_back();while (it1 != l1.end()){cout << *it1++ << " ";}cout << endl;// 头删完成之后需要更新一下it1。否则it1指向原来的位置,为空的迭代器l1.pop_front();it1 = l1.begin();while (it1 != l1.end()){cout << *it1++ << " ";}cout << endl;l1.push_front(0);l1.push_front(1);for (auto& e : l1){cout << e << " ";}cout << endl;
}void test_list05()
{dza::list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);dza::list<int> l2(l1);for (auto& e : l1){cout << e << " ";}cout << endl;for (auto& e : l2){cout << e << " ";}cout << endl;dza::list<int> l3(3,1);for (auto& e : l3){cout << e << " ";}cout << endl;l3.clear();for (auto& e : l3){cout << e << " ";}cout << endl;
}void test_list06()
{dza::list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);for (auto& e : l1){cout << e << " ";}cout << endl;auto it1 = l1.begin();it1++;it1++;l1.insert(it1, 30);for (auto& e : l1){cout << e << " ";}cout << endl;l1.erase(l1.begin());for (auto& e : l1){cout << e << " ";}cout << endl;}int main()
{test_list06();return 0;
}