从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,模拟实现。

目录
相关文章
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
191 13
|
25天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
65 0
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
172 4
|
3月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
3月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
85 1
|
3月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
58 2
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
103 6
|
4月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
65 3
|
3月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
46 0