【C++要笑着学】list 核心框架接口的模拟实现 | 运算符重载实现list迭代器 | 妙用模板实现const迭代器(二)

简介: 我们在上一章说过,list 其实就是带哨兵位循环双向链表而已,这种链表虽然结构复杂,但是实现起来反而是最简单的,我们在数据结构专栏中有过详细的讲解。

Ⅳ. list 的拷贝构造和赋值重载


0x00 引入:list 的同样涉及深浅拷贝问题

❓ 思考:这里的拷贝构造是深拷贝还是浅拷贝?


void test_list4() {
  list<int> L1;
  L1.push_back(1);
  L1.push_back(2);
  L1.push_back(3);
  list<int> L2(L1);
  for (auto e : L2) cout << e << " ";
  }

🚩 运行结果:

5005bab504d78b2ac1d7f394cac05901_0c494a3a535c416e9f9d655ee8184734.png


💡 这里默认生成的拷贝构造是浅拷贝:

ecf3cf2f79ab52b44cd9b985a55f7f6f_c378175626f1471fac8db1d9ffac9a31.png


比如这里 浅拷贝导致 L1 和 L2 指向同一块地址,析构的时候会导致一块空间被释放两次。


这些知识我们在之前讲解深浅拷贝的时候就说过,这里没崩仅仅是因为我们还没设计析构。


这里只是勉强 "逃过一杰" 。


综上所述,我们这里依然要自己去实现一个拷贝构造,去完成 "深拷贝" 。


我们下面实现一下 ~list,再回来看看。


0x01 clear 清空链表中所有数据

在实现链表 list 的析构函数前,为了方便清空,我们先实现一下 clear()


💬 代码:清空链表数据

/* 清空链表所有数据 */
void clear() {
  // 利用迭代器去遍历整个链表
  iterator cur = begin();
  while (cur != end()) {
  iterator del = cur++;  // 巧妙利用后置++的特性
  delete del._node;      // 删除当前结点,后置++生效
  }
  // 删完之后我们还需要将其恢复到初始状态
  _pHead->_next = _pHead;
  _pHead->_prev = _pHead;
}

🔨 测试代码:


void test_list5() {
  list<int> L;
  L.push_back(1);
  L.push_back(2);
  L.push_back(3);
  cout << "删除前:";
  print_list(L);
  cout << "删除后:";
  L.clear();
  print_list(L);
  }

🚩 运行结果:

fa918edb8cc89174c8b5a10911ce3ea7_712608ded6f8483cac01c59933a2c203.png


当然,这里的删除结点我们也可以用 erase 去完成。


用 erase 可以省去我们将其恢复 _pHead 的前驱和后继指向自己的操作。

e250e9e86ac4bb129e36795a9bf66a3d_69823265f37b4810b4b634f4b131eb3c.png

删除掉最后一个有效元素后, erase 的 "缝合操作" 自然会做到:


💬 代码:用迭代器 + erase 的方式去逐个访问并删除每个节点


void clear() {
  iterator cur = begin();
  while (cur != end()) {
  iterator del = cur++;
  erase(del);           // 直接反复调用erase删除结点
  }
}

⚡ 简化:还可以进一步简化:

/* 清空链表所有数据 */
void clear() {
  iterator cur = begin();
  while (cur != end()) {
  // iterator del = cur++;
  erase(cur++);   // 直接反复调用erase删除结点
  }
}

0x02 ~list 析构

实现了 clear 后,我们再去实现 list 的析构函数就很简单了。


我们只需要把哨兵位头结点 _pHead 给干掉就行了,并且记得要置成空指针。


💬 代码:析构函数的实现

~list() {
  // 清空链表有效数据
  clear();
  // 干掉头结点
  delete _pHead;
  _pHead = nullptr;
}

实现好了析构函数,我们再回过头来测刚才 "逃过一劫" 的浅拷贝导致的两个结点指向同一地址的问题,现在问题就变得严重起来了,因为它会被析构两次:

void test_list4() {
    list<int> L1;
    L1.push_back(1);
    L1.push_back(2);
    L1.push_back(3);
    list<int> L2(L1);
    for (auto e : L2) cout << e << " ";
  }

🚩 运行结果:(崩溃)


刚才得益于我们还没有去实现析构函数,让浅拷贝带来的问题 "逃过一杰" 。


但是现在可谓是在劫难逃了 ——

b8acae8c260ab4d1193ff1f7b947cb4e_30cbef58de654116907ca81977f31c1e.png


自动生成的拷贝构造是浅拷贝,为了解决这个问题,我们需要手动实现一个深拷贝的拷贝构造!


0x03 拷贝构造的实现

💬 代码:传统写法

/* 拷贝构造:L2(L1) */
list(const list<T>& L) {
  _pHead = new Node();
  _pHead->_next = _pHead;
  _pHead->_prev = _pHead;
  for (auto e : L) {
  push_back(e);
  }
}

💬 代码:现代写法


template<class InputIterator> 
list(InputIterator first, InputIterator last) {
  _pHead = new Node();
  _pHead->_next = _pHead;
  _pHead->_prev = _pHead;
  while (first != last) {
  push_back(*first);
  first++;
  }
}
/* 拷贝构造(现代写法):L2(L1) */
list(const list<T>& L) {
    _pHead = new Node();
  _pHead->_next = _pHead;
  _pHead->_prev = _pHead;
  list<T> tmp(L.begin(), L.end());
  swap(_pHead, tmp._pHead);  // 交换两个结点的指针
}

0x04 赋值重载

💬 代码:传统写法

/* 赋值:L2 = L1 */
list<T>& operator=(list<T> L) {
  if (this != &L) {
  clear();
  for (auto e : L) {
    push_back(e);
  }
  }
  return *this;
}

💬 代码:现代写法


/* 赋值(现代写法):L2 = L1 */
list<T>& operator=(list<T> L) {
  swap(_pHead, L._pHead);
  return *this;
}

Ⅴ. 完整代码


0x00 list.h

(注:未采用申明与定义分离的形式)


#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace chaos
{
  /* 定义结点 */
  template<class T>
  struct ListNode {
  T _data;      // 用来存放结点的数据
  ListNode<T>* _next;       // 指向后继结点的指针
  ListNode<T>* _prev;       // 指向前驱结点的指针
  ListNode(const T& data = T())   // 全缺省构造(初始化)
    : _data(data)
    , _next(nullptr)
    , _prev(nullptr)
  {}
  };
  /* 定义迭代器 */
  template<class T, class Ref, class Ptr>
  struct __list_iterator {
  typedef ListNode<T> Node;
  typedef __list_iterator<T, Ref, Ptr> self;    // 为了方便我们重命名为self
  typedef Ref reference;
  typedef Ptr pointer;
  Node* _node;
  __list_iterator(Node* x)
    : _node(x) 
  {}
  /* 解引用 */
  Ref operator*() {
    return _node->_data;       // 返回结点的数据
  }
  Ptr operator->() {
    return &_node->_data;     
  }
  /* ++it */
  self& operator++() {
    _node = _node->_next;  // 让 _node 指向下一个结点
    return *this;  // 返回加加后的值
  }
  /* it++ */
  self operator++(int) {
    self tmp(*this);   // 拷贝构造一个tmp存储原来的值
    _node = _node->_next;
    return tmp;
  }
  /* != */
  bool operator!=(const self& it) {
    return _node != it._node;  // 它们结点的指针不一样吗?T or F
  }
  /* --it */
  self& operator--() {
    _node = _node->_prev;  
    return *this;
  }
  /* it-- */
  self operator--(int) {
    self tmp(*this); 
    _node = _node->_prev;
    return tmp;
  }
  };
  ///* 定义const迭代器 */
  //template<class T>
  //struct __const_list_iterator {
  //  typedef ListNode<T> Node;
  //  Node* _node;
  //  __const_list_iterator(Node* x)
  //  : _node(x)
  //  {}
  //  /* 解引用 */
  //  const T& operator*() {
  //  return _node->_data;       // 返回结点的数据
  //  }
  //  /* ++it */
  //  __const_list_iterator<T>& operator++() {
  //  _node = _node->_next;  // 让 _node 指向下一个结点
  //  return *this;  // 返回加加后的值
  //  }
  //  /* it++ */
  //  __const_list_iterator<T> operator++(int) {
  //  __const_list_iterator<T> tmp(*this);   // 拷贝构造一个tmp存储原来的值
  //  _node = _node->_next;
  //  return tmp;
  //  }
  //  /* != */
  //  bool operator!=(const __const_list_iterator<T>& it) {
  //  return _node != it._node;  // 它们结点的指针不一样吗?T or F
  //  }
  //  /* --it */
  //  __const_list_iterator<T>& operator--() {
  //  _node = _node->_prev;
  //  return *this;
  //  }
  //  /* it-- */
  //  __const_list_iterator<T> operator--(int) {
  //  __list_iterator<T> tmp(*this);
  //  _node = _node->_prev;
  //  return tmp;
  //  }
  //};
  /* 定义链表 */
  template<class T>
  class list {
  typedef ListNode<T> Node;      // 重命名为Node
  public:
  /* 迭代器 */
  typedef __list_iterator<T, T&, T*> iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  iterator begin() { 
    return iterator(_pHead->_next); 
  }
  iterator end() { 
    return iterator(_pHead); 
  }
  const_iterator begin() const {
    return const_iterator(_pHead->_next);  
  }
  const_iterator end() const {
    return const_iterator(_pHead);
  }
  /* 构造函数:初始化头结点 */
  list() {
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
  }
  list(size_t n, const T& val = T()) {   // 初始化n个结点
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
    for (size_t i = 0; i < n; i++) {
    push_back(val);
    }
  }
  list(int n, const T& val = T()) {   // 初始化n个结点
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
    for (int i = 0; i < n; i++) {
    push_back(val);
    }
  }
  template<class InputIterator> 
  list(InputIterator first, InputIterator last) {
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
    while (first != last) {
    push_back(*first);
    first++;
    }
  }
  /* 拷贝构造(现代写法):L2(L1) */
  list(const list<T>& L) {
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
    list<T> tmp(L.begin(), L.end());
    swap(_pHead, tmp._pHead);
  }
  /* 拷贝构造:L2(L1) */
  /*list(const list<T>& L) {
    _pHead = new Node();
    _pHead->_next = _pHead;
    _pHead->_prev = _pHead;
    for (auto e : L) {
    push_back(e);
    }
  }*/
  ///* 赋值:L2 = L1 */
  //list<T>& operator=(list<T> L) {
  //  if (this != &L) {
  //  clear();
  //  for (auto e : L) {
  //    push_back(e);
  //  }
  //  }
  //  return *this;
  //}
  /* 赋值(现代写法):L2 = L1 */
  list<T>& operator=(list<T> L) {
    swap(_pHead, L._pHead);
    return *this;
  }
  /* 在pos位置前插入 */
  void insert(iterator pos, const T& x) {
    Node* cur = pos._node;     // 找到pos位置的结点
    Node* cur_prev = cur->_prev;     // 因为要在pos位置前插入,所以要找到当前pos位置的前一个结点
    Node* new_node = new Node(x);  // 创建新节点
    // 缝合: cur_prev <-> new_node <-> cur
    cur_prev->_next = new_node;
    new_node->_prev = cur_prev;
    new_node->_next = cur;
    cur->_prev = new_node;
  }
  /* 尾插:push_back */
  void push_back(const T& x) {
    //Node* pTail = _pHead->_prev;     // pHead的前驱就是pTail
    //Node* new_node = new Node(x);    // 创建新结点(会调用构造,自动创建)
    //
    //pTail->_next = new_node;
    //new_node->_prev = pTail;
    //new_node->_next = _pHead;
    //_pHead->_prev = new_node;
    insert(end(), x);   // 在end(头结点)前插入,即尾插
  }
  /* 头插:push_front */
  void push_front(const T& x) {
    insert(begin(), x);  // 在begin(头结点的下一个结点)前插入,即头插
  }
  /* 任意位置删除 */
  iterator erase(iterator pos) {
    assert(pos != end());   // 防止头结点被删除
    Node* cur = pos._node;   // 找到pos位置的结点
    Node* cur_prev = cur->_prev;   // 找到pos的前驱
    Node* cur_next = cur->_next;   // 找到pos的后继
    // 删除cur
    delete cur;
    // 缝合:  cur_prev <-> cur(删) <-> cur_next
    cur_prev->_next = cur_next;
    cur_next->_prev = cur_prev;
    return iterator(cur_next);
  }
  /* 尾删 */
  void pop_back() {
    erase(--end());  // 删除最后一个元素,即尾结点
  }
  /* 头删 */
  void pop_front() {
    erase(begin());  // 删除头结点的下一个结点(即begin位置的结点)
  }
  /* 清空链表所有数据 */
  void clear() {
    利用迭代器去遍历整个链表
    //iterator it = begin();
    //while (it != end()) {
    //  iterator del = it++;  // 巧妙利用后置++的特性
    //  delete del._node;  // 删除当前结点,后置++生效
    //}
    删完之后我们还需要将其恢复到初始状态
    //_pHead->_next = _pHead;
    //_pHead->_prev = _pHead;
    iterator it = begin();
    while (it != end()) {
    // iterator del = it++;
    erase(it++);   // 直接反复调用erase删除结点
    }
  }
  ~list() {
    // 清空链表有效数据
    clear();
    // 干掉头结点
    delete _pHead;
    _pHead = nullptr;
  }
  private:
  Node* _pHead;
  };
  void print_list(const list<int>& L) {
    list<int>::const_iterator it = L.begin();
    while (it != L.end()) {
    // *it = 100;
    cout << *it << " ";
    it++;
    }
    cout << endl;
  }
  void test_list1() {
    list<int> L;
    L.push_back(1);
    L.push_back(2);
    L.push_back(3);
    L.push_back(4); 
    list<int>::iterator it = L.begin();
    while (it != L.end()) {
    *it *= 2;
    cout << *it << " ";
    it++;
    }
    cout << endl;
  }
  void test_list2() {
    list<int> L;
    L.push_back(2);
    L.push_back(4);
    L.push_back(6);
    L.push_back(8);
    print_list(L);
  }
  struct Date {
    int _year;
    int _month;
    int _day;
    Date(int year = 1, int month = 1, int day = 1) 
    : _year(year)
    , _month(month)
    , _day(day) 
    {}
  };
  void test_list3() {
    list<Date> L;
    L.push_back(Date(2022, 5, 1));
    L.push_back(Date(2022, 5, 2));
    L.push_back(Date(2022, 5, 3));
    list<Date>::iterator it = L.begin();
    while (it != L.end()) {
    cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
    it++;
    }
    cout << endl;
  }
  void test_list4() {
    list<int> L1;
    L1.push_back(1);
    L1.push_back(2);
    L1.push_back(3);
    list<int> L2(L1);
    for (auto e : L2) cout << e << " ";
  }
  void test_list5() {
    list<int> L;
    L.push_back(1);
    L.push_back(2);
    L.push_back(3);
    cout << "删除前:";
    print_list(L);
    cout << "删除后:";
    L.clear();
    print_list(L);
  }
}

0x01 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "list.h"
int main(void)
{
  chaos::test_list1();
  return 0;
}

0x02 reverse_iterator.h

#define _CRT_SECURE_NO_WARNINGS 1
#include "list.h"
namespace chaos
{
  // Iterator是哪个容器的迭代器,reverse_iterator<Iterator>就可以
  // 适配出哪个容器的反向迭代器。复用的体现
  //template <class Iterator, class Ref, class Ptr>
  /* 定义反向迭代器 */
  template <class Iterator>
  class reverse_iterator {
  typedef reverse_iterator<Iterator, Ref, Ptr> self;
  typedef typename Iterator::Ref reference;  
  typedef typename Iterator::Ptr pointer;
  public:
  reverse_iterator(Iterator it)
    :_it(it)
  {}
  Ref operator*() {
    //return *_it;
    Iterator prev = _it;
    return *--prev;
  }
  Ptr operator->() {
    return &operator*();
  }
  self& operator++() {
    --_it;
    return *this;
  }
  self& operator--() {
    ++_it;
    return *this;
  }
  bool operator!= (const self& rit) const {
    return _it != rit._it;
  }
  private:
  Iterator _it;
  };
}


相关文章
|
11天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
17 2
|
22天前
|
编译器 C++
C++入门指南:10分钟带你快速了解模板究竟是什么(建议收藏!!)
C++入门指南:10分钟带你快速了解模板究竟是什么(建议收藏!!)
27 0
|
24天前
|
算法 C++ 开发者
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
35 0
|
3天前
|
存储 编译器 C++
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
|
11天前
使用Vant框架的组件van-pull-refresh搭配van-list和van-card完成上滑加载更多列表数据,下拉刷新当前列表数据(等同于翻页功能)
使用Vant框架的组件van-pull-refresh搭配van-list和van-card完成上滑加载更多列表数据,下拉刷新当前列表数据(等同于翻页功能)
|
11天前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
10 2
|
24天前
|
存储 算法 Linux
深入理解Linux内存管理brk 和 sbrk 与以及使用C++ list实现内存分配器
深入理解Linux内存管理brk 和 sbrk 与以及使用C++ list实现内存分配器
32 0
|
24天前
|
存储 程序员 编译器
【C++ 模板类与虚函数】解析C++中的多态与泛型
【C++ 模板类与虚函数】解析C++中的多态与泛型
46 0
|
24天前
|
算法 编译器 C++
【C++ 模板编程 基础知识】C++ 模板类部分特例化的参数顺序
【C++ 模板编程 基础知识】C++ 模板类部分特例化的参数顺序
21 0
|
24天前
|
算法 程序员 C++
【C++运算符重载】探究C++中的下标运算符[]重载
【C++运算符重载】探究C++中的下标运算符[]重载
14 0