从C语言到C++_17(list的模拟实现)list不是原生指针的迭代器(下 )

简介: 从C语言到C++_17(list的模拟实现)list不是原生指针的迭代器

从C语言到C++_17(list的模拟实现)list不是原生指针的迭代器(中):https://developer.aliyun.com/article/1521342

4.2 迭代器区间构造和交换

我们直接写现代写法,因为list本来就是提供迭代器区间初始化和交换函数的,

现在我们实现一下,并且拷贝构造的话至少保证有个头结点把,

所以我们把构造函数拎出来复用一下,写成一个empty_init 函数 (源码里也是这样写的),

这几个函数我们很熟了,直接放代码:

    void empty_init()// 创建并初始化哨兵位头结点(即构造函数)
    {
      _head = new Node;
      _head->_next = _head;
      _head->_prev = _head;
    }
    template <class InputIterator>
    list(InputIterator first, InputIterator last)
    {
      empty_init();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    list()
    {
      empty_init();
    }
    void swap(list<T>& x)//提一嘴:void swap(list& x)也行(类里面可以省略<T>,类外不行>
    {
      std::swap(_head, x._head); // 换哨兵位头结点就行
    }

4.3 拷贝构造和赋值重载

在上面的基础上我们直接用现代写法:

    list(const list<T>& lt)//lt2(lt1)
    {
      empty_init();
      list<T> tmp(lt.begin(), lt.end()); // 迭代器区间构造一个(lt1)
      swap(tmp); // 直接和lt2换哨兵位头结点
    }
    list<T>& operator=(list<T> lt)//lt3 = lt1 这里lt1直接深拷贝给lt,lt是局部对象,出来作用域直接调析构
    {
      swap(lt);// 把深拷贝出来的lt和lt3交换
      return *this; // 把lt3返回
    }

测试一下:

  void Test_copy() 
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
 
    list<int> lt2(lt);
    for (auto& e : lt2)
    {
      e *= 10;
    }
    print_list(lt);
    print_list(lt2);
  }


5. list相关选择题

1. 以下程序输出结果为( )

int main()
{
  int ar[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  int n = sizeof(ar) / sizeof(int);
  list<int> mylist(ar, ar + n);
  list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);
  reverse(mylist.begin(), pos);
  reverse(pos, mylist.end());
  list<int>::const_reverse_iterator crit = mylist.crbegin();
  while (crit != mylist.crend())
  {
    cout << *crit << " ";
    ++crit;
  }
  cout << endl;
}

A.4 3 2 1 0 5 6 7 8 9

B.0 1 2 3 4 9 8 7 6 5

C.5 6 7 8 9 0 1 2 3 4

D.5 6 7 8 9 4 3 2 1 0

2. 下面程序的输出结果正确的是( )

int main()
{
  int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };
  int n = sizeof(array) / sizeof(int);
  list<int> mylist(array, array + n);
  auto it = mylist.begin();
  while (it != mylist.end())
  {
    if (*it != 0)
      cout << *it << " ";
    else
      it = mylist.erase(it);
    ++it;
  }
  return 0;
}

A.1 2 3 4 5 6 7 8 9

B. 1 2 3 4 6 7 8 9

C.程序运行崩溃

D.1 2 3 4 0 5 6 7 8 9

3. 对于list有迭代器it, 当erase(it)后,说法错误的是( )

A.当前迭代器it失效

B.it前面的迭代器仍然有效

C.it后面的迭代器失效

D.it后面的迭代器仍然有效

4. 下面有关vector和list的区别,描述错误的是( )

A.vector拥有一段连续的内存空间,因此支持随机读取,如果需要高效的随机读取,应该使用vector

B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用listC.vector<int>::iterator支持“+”、“+=”、“<”等操作符

D.list<int>::iterator则不支持“+”、“+=”、“<”等操作符运算,但是支持了[]运算符

5. 下面有关vector和list的区别,描述正确的是( )

A.两者在尾部插入的效率一样高

B.两者在头部插入的效率一样高

C.两者都提供了push_back和push_front方法

D.两者都提供了迭代器,且迭代器都支持随机访问


6. 以下代码实现了从 list 中删除重复项的功能,请选择其中空白行应填入的正确代码( )

template<typename T>
void removeDuplicates(list<T>& aList)
{
  T curValue;
  list<T>::iterator cur, p;
  cur = aList.begin();
  while (cur != aList.end())
  {
    curValue = *cur;
    //空白行 1
    while (p != aList.end())
    {
      if (*p == curValue)
      {
        //空白行 2
      }
      else
      {
        p++;
      }
    }
  }
}

A. p=cur+1;aList.erase(p++);


B.p=++cur; p == cur ? cur = p = aList.erase(p) : p = aList.erase(p);


C.p=cur+1;aList.erase(p);


D.p=++cur;aList.erase(p);

答案:

1. C

分析:list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5); //找到5的位置


reverse(mylist.begin(), pos);//逆置0 1 2 3 4 为 4 3 2 1 0


reverse(pos, mylist.end()); //逆置5 6 7 8 9 为 9 8 7 6 5


逆置完成之后其数据为:4 3 2 1 0 9 8 7 6 5


list<int>::const_reverse_iterator crit = mylist.crbegin(); //反向迭代器进行反向访问


while(crit != mylist.crend()){}


所以答案为:5 6 7 8 9 0 1 2 3 4


2. B


分析:程序在使用迭代器取值时,如果不等于0就进行打印,为0时不打印并删除当前节点,且返回删除结点的下一个结点。


3. C


分析:删除节点后,只有指向当前节点的迭代器失效了,其前后的迭代器仍然有效,因为底层为不连续空间,只有被删除的   节点才会失效。


4. D


A.如果想大量随机读取数据操作,vector是首选的容器


B.如果想大量的插入和删除数据,list效率较高,是首选


C.由于vector底层是连续空间,其迭代器就是相应类型的指针,所以支持对应的操作


D.list迭代器不支持[]运算符


5. A


A.vector在尾部插入数据不需要移动数据,list为双向循环链表也很容易找到尾部,因此两者在尾部插入数据效率相同


B.vector头部插入效率极其低,需要移动大量数据


C.vector由于在头部插入数据效率很低,所以没有提供push_front方法


D.list不支持随机访问


6. B


分析:迭代p需要迭代不重复节点的下一节点,重要的是cur迭代器需要往下迭代,因此cur需要往前移动,二答案A C的cur都不会改变,空白行2是当需要找到重复值时进行节点删除,当删除时当前迭代器会失效,因此需要将迭代器p往后迭代。

6. 完整代码

list.h

#pragma once
 
#include<iostream>
#include<assert.h>
using namespace std;
 
namespace rtx
{
  template<class T>
  struct list_node // 定义结点
  {
    T _data;
    list_node* _prev;
    list_node* _next;
 
    list_node(const T& x = T())
      :_data(x)
      ,_prev(nullptr)
      ,_next(nullptr)
    {}
  };
 
  template<class T, class Ref, class Ptr>
  struct __list_iterator // 定义迭代器
  {
    typedef list_node<T> Node;
    typedef __list_iterator<T, Ref, Ptr> iterator;
    // 在list类里面:
    // typedef __list_iterator<T, T&, T*>             iterator;
        // typedef __list_iterator<T, const T&, const T*> const_iterator;
    Node* _node;
 
    __list_iterator(Node* node)
      :_node(node)
    {}
 
    bool operator!=(const iterator& it)
    {
      return _node != it._node;
    }
    //T& operator*()
    Ref operator*()
    {
      return _node->_data;  // 返回结点的数据
    }
    //T* operator->()
    Ptr operator->()
    {
      return &(operator*());
      //即 return &(_node->_data);
    }
    iterator& operator++()
    {
      _node = _node->_next;
      return *this; // 返回加加后的值
    }
    iterator operator++(int)
    {
      iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
      _node = _node->_next;
      return tmp; // 返回加加前的值
    }
    iterator& operator--()
    {
      _node = _node->_prev;
      return *this; // 返回减减后的值
    }
    iterator operator--(int)
    {
      iterator tmp(*this); // 拷贝构造一个tmp存储原来的值
      _node = _node->prev;
      return tmp; // 返回减减前的值
    }
  };
 
  template<class T>
  class list // 定义list类
  {
    typedef list_node<T> Node;
  public:
    typedef __list_iterator<T, T&, T*> iterator;
    typedef __list_iterator<T, const T&, const T*> const_iterator;
 
    const_iterator begin() const
    {
      return const_iterator(_head->_next);
    }
    const_iterator end() const
    {
      return const_iterator(_head);
    }
 
    iterator begin()// begin是实际数据的第一个,_head 是哨兵位头结点
    {
      return iterator(_head->_next);
    }
    iterator end()// end是实际数据的下一个
    {
      return iterator(_head);
    }
 
    void empty_init()// 创建并初始化哨兵位头结点(即构造函数)
    {
      _head = new Node;
      _head->_next = _head;
      _head->_prev = _head;
    }
    template <class InputIterator>
    list(InputIterator first, InputIterator last)
    {
      empty_init();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    list()
    {
      empty_init();
    }
    void swap(list<T>& x)//提一嘴:void swap(list& x)也行(类里面可以省略<T>,类外不行>
    {
      std::swap(_head, x._head); // 换哨兵位头结点就行
    }
 
    list(const list<T>& lt)//lt2(lt1)
    {
      empty_init();
      list<T> tmp(lt.begin(), lt.end()); // 迭代器区间构造一个(lt1)
      swap(tmp); // 直接和lt2换哨兵位头结点
    }
    list<T>& operator=(list<T> lt)//lt3 = lt1 这里lt1直接深拷贝给lt,lt是局部对象,出来作用域直接调析构
    {
      swap(lt);// 把深拷贝出来的lt和lt3交换
      return *this; // 把lt3返回
    }
 
    ~list()
    {
      clear();
      delete _head;
      _head = nullptr;
    }
    void clear()
    {
      iterator it = begin();
      while (it != end())
      {
        it = erase(it); // 返回删除位置的下一个结点
      }
    }
 
    iterator 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;
 
      return iterator(NewNode);
    }
    void push_back(const T& x)
    {
      //Node* tail = _head->_prev;
      //Node* NewNode = new Node(x);
       思路图:head        tail  NewNode
      //tail->_next = NewNode;
      //NewNode->_prev = tail;
      //_head->_prev = NewNode;
      //NewNode->_next = _head;
      insert(end(), x);
    }
    void push_front(const T& x)
    {
      insert(begin(), x);
    }
 
    iterator erase(iterator pos)
    {
      assert(pos != end());
      Node* cur = pos._node; // 删cur
      Node* prev = cur->_prev;
 
      prev->_next = cur->_next; // cur前一个指向cur后一个
      cur->_next->_prev = prev; // cur后一个指回cur前一个
 
      delete cur;
      return iterator(prev->_next); // 返回删除位置下一个
    }
    void pop_back()
    {
      erase(begin());
    }
    void pop_front()
    {
      erase(--end());
    }
 
  private:
    Node* _head; // 哨兵位头结点
  };
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
 
#include "list.h"
 
namespace rtx
{
  void Test_arrow()
  {
    struct Pos
    {
      int _a1;
      int _a2;
 
      Pos(int a1 = 0, int a2 = 0)
        :_a1(a1)
        , _a2(a2)
      {}
    };
    Pos aa;
    Pos* p2 = &aa;
    p2->_a1;
    p2->_a2;
 
    list<Pos> lt;
    lt.push_back(Pos(10, 20));
    lt.push_back(Pos(10, 21));
 
    list<Pos>::iterator it = lt.begin();
    while (it != lt.end())
    {
      //cout << (*it)._a1 << "," << (*it)._a2 << endl;
      cout << it->_a1 << "," << it->_a2 << endl;
        //实际要写,it->->_a1,但是编译器优化了一个箭头
      ++it;
    }
    cout << endl;
  }
 
  void print_list(const list<int>& lt) 
  {
    list<int>::const_iterator it = lt.begin();
    while (it != lt.end())
    {
      cout << *it << " ";
      it++;
    }
    cout << endl;
  }
 
  void Test_push_back()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
 
    list<int>::iterator it = lt.begin();
    print_list(lt);
 
    for (const auto& e : lt)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void Test_insert()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
    //list<int>::iterator pos = find(lt.begin(), lt.end(), 2);// 涉及其它问题,先不这样写
    //if (pos != lt.end())
    //{
    //  lt.insert(pos, 20);
    //}
    lt.insert(++lt.begin(), 20);
    lt.push_front(0);
    lt.push_front(-1);
    print_list(lt);
  }
 
  void Test_erase()
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
 
    lt.erase(++++lt.begin());//发现个好玩的,(删除3)
    lt.pop_back();
    lt.pop_front();
    print_list(lt);
  }
 
  void Test_copy() 
  {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.push_back(5);
 
    list<int> lt2(lt);
    for (auto& e : lt2)
    {
      e *= 10;
    }
    print_list(lt);
    print_list(lt2);
  }
}
 
int main()
{
  //rtx::Test_push_back();
  //rtx::Test_arrow();
  //rtx::Test_insert();
  //rtx::Test_erase();
  rtx::Test_copy();
 
  return 0;
}

本篇完。

list 的反向迭代器放在后面栈和队列期间讲,下一部分:栈和队列:使用,OJ,模拟实现。

目录
相关文章
|
9天前
|
安全 程序员 编译器
【C语言基础】:深入理解指针(二)
【C语言基础】:深入理解指针(二)
【C语言基础】:深入理解指针(二)
|
2天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。
|
3天前
|
存储 算法 C++
C++一分钟之-容器概览:vector, list, deque
【6月更文挑战第21天】STL中的`vector`是动态数组,适合随机访问,但插入删除非末尾元素较慢;`list`是双向链表,插入删除快但随机访问效率低;`deque`结合两者优点,支持快速双端操作。选择容器要考虑操作频率、内存占用和性能需求。注意预分配容量以减少`vector`的内存重分配,使用迭代器而非索引操作`list`,并利用`deque`的两端优势。理解容器内部机制和应用场景是优化C++程序的关键。
18 5
|
9天前
|
C语言
【C语言基础】:深入理解指针(终篇)
【C语言基础】:深入理解指针(终篇)
|
9天前
|
存储 C语言 C++
【C语言基础】:深入理解指针(三)
【C语言基础】:深入理解指针(三)
|
9天前
|
存储 编译器 C语言
【C语言基础】:深入理解指针(一)
【C语言基础】:深入理解指针(一)
|
11天前
|
C语言 C++ 编译器
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
|
2天前
|
C语言
C语言--深入指针(1)二刷
C语言--深入指针(1)二刷
|
2天前
|
C语言
C语言----深入理解指针(3)(二)
C语言----深入理解指针(3)
|
3天前
|
C语言
C语言----指针模拟二维数组
C语言----指针模拟二维数组