0、前言:
类中的 list 是双向带头循环链表
我们这里模拟实现的 list 也是 双向带头循环链表
1、list 的核心框架实现(list 类模板)
1.1 节点类模板 和 链表类模板
1.1.1 节点类
双向链表节点(含有 prev 指针 和 next 指针)
cpptemplate<class T> struct ListNode { typedef ListNode<T> Node; Node* _next; Node* _prev; T _data; // 构造函数 ListNode(const T& data = T()) :_data(data) , _next(nullptr) , _prev(nullptr) {} };
【思考】为什么 节点类 用的是 struct ,而不是 class?
先说明:struct 和 class 的区别
因为 struct 在 C++语法中,和 class 都可以表示类
struct 是C++兼容 C语言的 语法
在 C++中 struct 的类,没有访问限定符的限制
所有成员一律 视为公有
再说明:为什么节点类要用 struct依据C语言学过的链表,在一个链表节点中,指向前后节点的指针和存储该节点数据的变量 都需要频繁的被外界访问(_next、_prev、_data),与其在 class 中使用 public 访问限定符,不如干脆直接使用 struct ,使该类中的所有成员都可被外界直接,提高便捷性
1.1.2 链表类
带头链表:含有一个 哨兵位节点,成员变量 _head 指向该哨兵位节点
cpptemplate<class T> class list { typedef ListNode<T> Node; // 将节点类型重命名 private: Node* _head; };
1.2 构造函数
作用:初始化一个 头节点(即哨兵位)
cpp// 构造函数:创建头节点 list() { empty_list(); _head = new Node(); _head->_next = _head; _head->_prev = _head; // 注意:_next 和 _prev 都是指向 _head,不是 nullptr !!!,否则 push_back 时会 有访问空指针的问题 }
2. 迭代器类模板(重点:理解迭代器思想)
2.1 迭代器思想与封装思想
你一个内置类型,无法完成的操作与行为,可以将该类型封装成一个类,在类里面重载各种运算,使这个内置类型间接拥有之前没有 "功能"
例如:
在链表中,有指向一个节点的指针 Node* _node;
这个指针是不能直接通过 ++ 操作,跳转到下一个节点的(链表空间不连续,不能像 vector 一样通过 原生指针 T* 的 ++操作 到达下一个元素位置)
此时可以将该指针封装成一个类 ,在类里面重载++操作,间接的使指针 _ node 有了 [ 指针++ ] 就跳转到下个节点的 功能(因为重载函数中的操作:_node = _ node-> _next; 使 _node 指向下一个节点位置)
cpptemplate<class T> struct NewPtr { typedef ListNode<T> Node; typedef NewPtr<T> Self; // 自己这种类型 Node* _node; // 核心还是 一个链表节点指针 // 构造函数 // 这里的 p 不用给 const ,否则权限放大 NewPtr(Node* p = nullptr) :_node(p) {} Self& operator++() { _node = _node->_next; return *this; } };
小结:
内置类型不能直接使用的行为,我们可以将这个内置类型封装成一个类型,在类型中通过重载运算符控制它的行为
因此:将 list 的节点指针 封装成一个迭代器类,使该指针拥有某些之前无法单独实现的 功能
迭代器的表面上是一个类型的壳
在内心实际上是一个节点的指针
2.2 链表的迭代器 ListIterator
按上面的逻辑:设计链表的迭代器 ListIterator
cpp// 迭代器就是将指向一个节点的指针 node 封装成一个类 template<class T> struct ListIterator { typedef ListNode<T> Node; typedef ListIterator<T> Self; // 自己这种类型 Node* _node; // 构造函数 // 这里的 p 不用给 const ,否则权限放大 ListIterator(Node* p = nullptr) :_node(p) {} // ++ -- Self& operator++() { _node = _node->_next; return *this; } };
2.3 补充迭代器函数:后置++ 和 前后置--
cppSelf operator++(int) { Self tmp(this->_node); _node = _node->_next; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } Self operator--(int) { Self tmp(this->_node); _node = _node->_prev; return tmp; }
2.4 迭代器 begin() / end()
注意:返回的也是迭代器,这里使用了 匿名对象(调用构造函数)
cppiterator begin() { return iterator(_head->_next); } iterator end() { return iterator(_head); }
2.5 范围for循环 需要的重载函数:不等于:!= 与 解引用:*
除了 begin() 和 end() 使范围for循环 生效,还需要题目中的这两个重载函数
等于:== 与 箭头操作符:-> 是为了匹配,作为相应的补充
cpp// == != // 范围for 需要使用 != 重载函数 bool operator!=(const Self& it) { return (this->_node != it._node); } // ==: 补充 bool operator==(const Self& it) { return (this->_node == it._node); } // * -> // 范围for 需要使用 * 重载函数 T& operator*() { return _node->_data; } // ->: 补充 T* operator->() { return &(_node->_data); }
2.6 ⭐迭代器 iterator 完整源码⭐
cpp// 迭代器就是将指向一个节点的指针 node 封装成一个类 template<class T> struct ListIterator { typedef ListNode<T> Node; typedef ListIterator<T> Self; // 自己这种类型 Node* _node; // 构造函数 // 这里的 p 不用给 const ,否则权限放大 ListIterator(Node* p = nullptr) :_node(p) {} // ++ -- Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(this->_node); _node = _node->_next; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } Self operator--(int) { Self tmp(this->_node); _node = _node->_prev; return tmp; } // == != // 范围for 需要使用 != 重载函数 bool operator!=(const Self& it) { return (this->_node != it._node); } // ==: 补充 bool operator==(const Self& it) { return (this->_node == it._node); } // * -> // 范围for 需要使用 * 重载函数 T& operator*() { return _node->_data; } // ->: 补充 T* operator->() { return &(_node->_data); } };
3、list 类中其他函数
3.1 插入 / 删除:insert / erase
插入:在 迭代器 pos 指向的节点前面插入 新节点
删除:删除 迭代器 pos 指向的节点
(新节点的插入 和 节点的删除 的具体操作这里不做讲解,若不明白,可以先学C语言的链表部分)
cppvoid insert(const iterator& pos, const T& x) { assert(pos._node); Node* newNode = new Node(x); Node* next = pos._node; Node* prev = next->_prev; // prev newNode next prev->_next = newNode; newNode->_prev = prev; newNode->_next = next; next->_prev = newNode; } void erase(const iterator& pos) { assert(pos._node); Node* cur = pos._node; Node* next = cur->_next; Node* prev = cur->_prev; // prev cur next delete cur; cur = nullptr; next->_prev = prev; prev->_next = next; }
【思考】insert 和 erase 是否存在迭代器失效?
根据之前的文章讲解:
迭代器失效往往是因为空间扩容 或 缩容,导致迭代器指针指向一块已经释放的空间,导致"野指针"的问题
链表的insert 函数 中,不存在因为扩容而另外开辟新空间的操作,迭代器 pos 从始至终都指向 合法的链表节点,因此没有 迭代器失效问题
链表的 erase 函数 中,删除 迭代器 pos 指向的节点后,迭代器 pos 就指向了一块已经释放的空间,造成迭代器失效问题
解决办法:学习库里的做法,返回迭代器
erase 函数 返回 指向 被删除节点的下一个节点的 迭代器
cppiterator insert(const iterator& pos, const T& x) { // ...... return iterator(newNode); } iterator erase(const iterator& pos) { // ...... return iterator(next); }
3.2 push_back / pop_back
push_back
cppvoid push_back(const T& x) { Node* newNode = new Node(x); Node* tail = _head->_prev; // tail newNode _head tail->_next = newNode; newNode->_prev = tail; newNode->_next = _head; _head->_prev = newNode; }
可以直接复用 insert 函数:即尾插,在 end() 前面插入一个节点
cppvoid push_back(const T& x) { insert(end(), x); }
**pop_back:**就直接复用 erase 函数
cppvoid pop_back() { assert(_head->_next != _head); erase(--end()); }
3.3 push_front / pop_front
这个就简单了,和 实现 push_back / pop_back 思想一样
cpp// push_front / pop_front void push_front(const T& x) { insert(begin(), x); } void pop_front() { assert(_head->_next != _head); erase(begin()); }
3.4 拷贝构造
根据传过来的参数的类型是否是 const 修饰,这里写两种
cpp// 空链表初始化 void empty_list() { _head = new Node(); _head->_next = _head; _head->_prev = _head; // 注意:_next 和 _prev 都是指向 _head,不是 nullptr !!!,否则 push_back 时会 有访问空指针的问题 } // 构造函数:创建头节点 list() { empty_list(); } // 拷贝构造 list(list<T>& L) { empty_list(); for (auto& e : L) { push_back(e); } } list(const list<T>& L) { empty_list(); for (auto& e : L) { push_back(e); } }
因为拷贝一个 链表 ,你自己本身也必须现有头节点(哨兵位),也就是 构造函数做的工作
因此,我们将 初始化一个哨兵位 的工作直接封装成一个函数 empty_list(),给 构造函数、拷贝构造函数等其他函数复用
【思考】为什么要显式的写拷贝构造函数?
因为默认的拷贝构造函数是浅拷贝
直接浅拷贝会导致 析构函数多次释放同一节点的空间,形成悬挂指针,造成程序崩溃
因此要显式的 写 深拷贝函数
3.5 initializer_list 构造
cpp// initializer_list 构造 list(initializer_list<T> il) { empty_list(); // 复用函数:初始化一个哨兵位出来 for (auto& e : il) { push_back(e); } }
3.6 赋值运算符重载
这里的赋值 使用的是 STL 的现代写法:使用 swap 交换大法
cpp// 赋值:由这个可以想到显式写 拷贝构造函数(默认的是值拷贝) list<T>& operator==(const list<T>& L) { list<T> tmp(L._head); std::swap(_head, tmp._head); return *this; }
3.7 析构函数
【思考】为什么不能直接使用 默认的析构函数:
默认的析构函数 只会讲 list 类中的 _head 直接析构释放,却不会一个一个的析构链表节点,导致只有头节点(即 _head 指向的 哨兵位)被释放,其他的节点不释放,造成内存泄漏
cpp// 析构 ~list() { clear(); delete _head; _head = nullptr; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } }
4. 迭代器类 const_iterator 及相关 const 修饰 函数
4.1 设计迭代器类模板 提高代码复用性
这个和前面的迭代器 iterator 类,没什么区别,const_iterator 就表示 迭代器指向的内容不能被修改,在 list 中 const_iterator 迭代器指向一个节点,即表示节点中的数据 _data 不能被修改
和 迭代器 iterator 类 主要的区别是: 下面这两个函数的返回值问题
- 第一个函数返回 引用,涉及 _data 的修改,要用 const 修饰起来
- 第二个函数返回 指针,涉及 _data 的修改,要用 const 修饰起来
cppT& operator*() { return _node->_data; } T* operator->() { return &(_node->_data); }
即修改成:
cppconst T& operator*() { return _node->_data; } const T* operator->() { return &(_node->_data); }
因此,就可以设计一个 const _iterator 的迭代器类:ListConstIterator
cpp// 迭代器就是将指向一个节点的指针 node 封装成一个类 template<class T> struct ListConstIterator { typedef ListNode<T> Node; typedef ListConstIterator<T> Self; // 自己这种类型 Node* _node; // 构造函数 // 这里的 p 不用给 const ,否则权限放大 ListConstIterator(Node* p = nullptr) :_node(p) {} // ++ -- Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(this->_node); _node = _node->_next; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } Self operator--(int) { Self tmp(this->_node); _node = _node->_prev; return tmp; } // == != // 范围for 需要使用 != 重载函数 bool operator!=(const Self& it) { return (this->_node != it._node); } // ==: 补充 bool operator==(const Self& it) { return (this->_node == it._node); } // * -> // 范围for 需要使用 * 重载函数 const T& operator*() { return _node->_data; } // ->: 补充 const T* operator->() { return &(_node->_data); } };
但是 iterator 的 ListIterator 类 和 const _iterator 的 ListConstIterator 类
就仅仅是 两个函数的返回值不同,其他代码的重复性过高了
⭐如何设计复用?
可以 利用 模板的性质,将这个 迭代器类 设计成可以变换 两个函数的返回值 的模板
给 迭代器类 增加两个 模板参数:Ref 和 Ptr
cpp
template<class T, class Ref, class Ptr>
Ref 是 引用 的缩写
Ptr 是 指针 的缩写
然后将 那 两个函数的返回值 更换成 这两个模板参数
这样之后,就能 根据传过来的 参数,来产生不同的 迭代器类对象
cppRef operator*() { return _node->_data; } Ptr operator->() { return &(_node->_data); }
⭐调用 迭代器类模板演示:
在 list 类中 设置两个 typedef :iterator 与 const_iterator 以区分不同类型
传不同的 Ref 和 Ptr 过去
cpptypedef ListIterator<T, T&, T*> iterator; typedef ListIterator<T, const T&, const T*> const_iterator;
4.2 ⭐最终的 迭代器类模板 完全体!⭐
cpp// 迭代器就是将指向一个节点的指针 node 封装成一个类 template<class T, class Ref, class Ptr> struct ListIterator { typedef ListNode<T> Node; typedef ListIterator<T, Ref, Ptr> Self; // 自己这种类型 Node* _node; // 构造函数 // 这里的 p 不用给 const ,否则权限放大 ListIterator(Node* p = nullptr) :_node(p) {} // ++ -- Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(this->_node); _node = _node->_next; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } Self operator--(int) { Self tmp(this->_node); _node = _node->_prev; return tmp; } // == != // 范围for 需要使用 != 重载函数 bool operator!=(const Self& it) { return (this->_node != it._node); } // ==: 补充 bool operator==(const Self& it) { return (this->_node == it._node); } // * -> // 范围for 需要使用 * 重载函数 Ref operator*() { return _node->_data; } // ->: 补充 Ptr operator->() { return &(_node->_data); } };
4.3 begin() 和 end() 也更新一套 const_iterator 版本的
cppconst_iterator begin() const { return const_iterator(_head->_next); } const_iterator end() const { return const_iterator(_head); }
5. 完整 list 类 源码
cppnamespace my { template<class T> struct ListNode { typedef ListNode<T> Node; Node* _next; Node* _prev; T _data; // 构造函数 ListNode(const T& data = T()) :_data(data) ,_next(nullptr) , _prev(nullptr) {} }; // 迭代器就是将指向一个节点的指针 node 封装成一个类 template<class T, class Ref, class Ptr> struct ListIterator { typedef ListNode<T> Node; typedef ListIterator<T, Ref, Ptr> Self; // 自己这种类型 Node* _node; // 构造函数 // 这里的 p 不用给 const ,否则权限放大 ListIterator(Node* p = nullptr) :_node(p) {} // ++ -- Self& operator++() { _node = _node->_next; return *this; } Self operator++(int) { Self tmp(this->_node); _node = _node->_next; return tmp; } Self& operator--() { _node = _node->_prev; return *this; } Self operator--(int) { Self tmp(this->_node); _node = _node->_prev; return tmp; } // == != // 范围for 需要使用 != 重载函数 bool operator!=(const Self& it) { return (this->_node != it._node); } // ==: 补充 bool operator==(const Self& it) { return (this->_node == it._node); } // * -> // 范围for 需要使用 * 重载函数 Ref operator*() { return _node->_data; } // ->: 补充 Ptr operator->() { return &(_node->_data); } }; template<class T> class list { typedef ListNode<T> Node; public: typedef ListIterator<T, T&, T*> iterator; typedef ListIterator<T, const T&, const T*> const_iterator; // 空链表初始化 void empty_list() { _head = new Node(); _head->_next = _head; _head->_prev = _head; // 注意:_next 和 _prev 都是指向 _head,不是 nullptr !!!,否则 push_back 时会 有访问空指针的问题 } // 构造函数:创建头节点 list() { empty_list(); } // 拷贝构造 list(list<T>& L) { empty_list(); for (auto& e : L) { push_back(e); } } list(const list<T>& L) { empty_list(); for (auto& e : L) { push_back(e); } } // initializer_list 构造 list(initializer_list<T> il) { empty_list(); for (auto& e : il) { push_back(e); } } // 析构 ~list() { clear(); delete _head; _head = nullptr; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } // 迭代器:begin(),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); } // push_back / pop_back void push_back(const T& x) { Node* newNode = new Node(x); Node* tail = _head->_prev; // tail newNode _head tail->_next = newNode; newNode->_prev = tail; newNode->_next = _head; _head->_prev = newNode; } void pop_back() { assert(_head->_next != _head); erase(--end()); } // push_front / pop_front void push_front(const T& x) { insert(begin(), x); } void pop_front() { erase(begin()); } // insert 和 erase // insert 无迭代器失效 iterator insert(const iterator& pos, const T& x) { assert(pos._node); Node* newNode = new Node(x); Node* next = pos._node; Node* prev = next->_prev; // prev newNode next prev->_next = newNode; newNode->_prev = prev; newNode->_next = next; next->_prev = newNode; return iterator(newNode); } iterator erase(const iterator& pos) { assert(pos._node); Node* cur = pos._node; Node* next = cur->_next; Node* prev = cur->_prev; // prev cur next delete cur; cur = nullptr; next->_prev = prev; prev->_next = next; return iterator(next); } // 赋值:由这个可以想到显式写 拷贝构造函数(默认的是值拷贝) list<T>& operator==(list<T>& L) { list<T> tmp(L._head); std::swap(_head, tmp._head); return *this; } private: Node* _head; }; }