温馨提示:这篇文章已超过446天没有更新,请注意相关的内容是否还可用!
摘要:C++ STL中的list容器是一种双向链表结构,其底层模拟实现主要包括节点的创建、插入、删除、遍历等操作。节点创建涉及动态内存分配和节点数据的存储;插入和删除操作需要维护链表的完整性,确保节点间的逻辑关系正确;遍历操作则是通过迭代器实现,可以方便地访问list中的每个元素。底层模拟实现还需要考虑性能优化和异常处理等方面。
目录
前言:
1.创建节点
2.普通迭代器的封装
3.反向迭代器的封装
为什么要对正向迭代器进行封装?
4.const迭代器
5.构造函数
6.拷贝构造
7.赋值重载
8.insert
9.erase
10.析构
11.头插头删,尾插尾删
12.完整代码+简单测试
总结:
前言:
模拟实现list,本篇的重点就是由于list是一个双向循环链表结构,所以我们对迭代器的实现不能是简单的指针的++,--了,因为我们知道,链表的存储不一定是连续的,所以直接++,--是链接不起来节点的,所以我们要对迭代器也就是对节点的指针进行封装。结尾会附上完整的代码。
1.创建节点
template struct 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.普通迭代器的封装
template struct _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(); } template list(Iterator first, Iterator end) { empty_Init();//别忘加上哨兵位,没有哨兵位识别不了end while (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,而拷贝构造要加const swap(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 once namespace my_iterator { template struct 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 { template struct list_node { list_node* _prev; list_node* _next; T _data; list_node(const T& x= T())//这里不给缺省值可能会因为没有默认构造函数而编不过 :_prev(nullptr) ,_next(nullptr) ,_data(x) {} }; template struct _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; } }; template class 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(); } template list(Iterator first, Iterator end) { empty_Init();//别忘加上哨兵位,没有哨兵位识别不了end while (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,而拷贝构造要加const swap(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
还没有评论,来说两句吧...