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

目录
相关文章
|
3月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
142 26
|
8月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
151 0
|
10月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
319 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
10月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1192 9
|
10月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
304 7
|
11月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
951 13
|
11月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
342 11
|
11月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
11月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
11月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
633 3

热门文章

最新文章