C++:list容器(非原生指针迭代器的实现)

本章是STL容器 list 的模拟实现。
之前已经使用 C语言 对带头双向循环链表 进行实现,详见数据结构: 线性表(带头双向循环链表实现), 相较于之前的实现,C++ 下多了对迭代器以及模板等相关语法特性。下面将着重讲解这些新知识。

文章目录

  • 一. list 的基本框架
    • 1. 结点的结构
    • 2. 链表初始化
    • 3. push_back 尾插
  • 二. list 迭代器的实现
    • 1. 迭代器的结构
    • 2. ++,--,*,==,!=
    • 3. ->
    • 4. 利用模板实现const迭代器
  • 三. 完整代码
    • list.h
    • test.cpp

一. list 的基本框架

1. 结点的结构

n个结点链接成一个链表,首先要构造结点的结构,C语言中结点是这样定义的:
在这里插入图片描述

虽然可以用 typedef 使得该结点可以存放不同的数据类型,但是如果在一个程序中有两个不同数据类型的链表,就需要再重新创建新的结点结构体,与此同时,链表的相关操作也需要进行重新创建。这样,一个程序中就有两个近乎相同的一长串代码,C++ 的模板此时就给了完美的解决方案:

// ListNode
template <typename T>
struct ListNode
{
  ListNode<T> *_next; // 指向后继结点的指针
  ListNode<T> *_prev; // 指向前驱结点的指针
  T _data;            // 存放结点的数据
};

通过类模板即可以在创建链表的时候指定结点的类型,以此推导出 T 的类型。

由于 C++ 中的关键字 struct 升级成了一个类, 这样就可以通过创建结点类的默认构造函数来实现结点的默认初始化。
STL 中 list 是一个带头双向循环链表,那么结点初始化的时候,可以使其的前驱后继都指向空指针, 同时数据域的初始化调用结点类型的默认构造函数。

// ListNode
template <typename T>
struct ListNode
{
  ListNode<T> *_next; // 指向后继结点的指针
  ListNode<T> *_prev; // 指向前驱结点的指针
  T _data;            // 存放结点的数据

  ListNode(const T &val = T()) // 全缺省构造
      : _next(nullptr), _prev(nullptr), _data(val)
  {
  }
};

2. 链表初始化

设计完结点的结构,接下来就是 List 类的构建, 为了方便使用,使用 typedefListNode<T> 进行重命名。
List 只有一个成员,就是指向头结点即哨兵位的指针。
构造函数也可以写出来了,创建一个新结点,该结点的前驱和后继指向自己,同时 _head 的值为该结点的地址。为了方便拷贝构造以及其他构造函数复用,这里将这个操作封装成一个私有函数。

namespace wr
{
  template <typename T>
  class list
  {
    typedef ListNode<T> Node;

  public:
    list()
    {
      empty_init():
    }

  private:
    void empty_init()
    {
      _head = new Node;
      _head->_prev = _head;
      _head->_next = _head;
    }
    Node* _head;
  };
}

3. push_back 尾插

此时完成尾插操作的实现,就可以把一个链表的最初框架完成了,尾插的实现就不过多赘述了。

push_back(const T &val = T())
{
  Node* newNode = new Node(val);
  Node* tail = _head->_prev;

  // tail newNode _head
  tail->_next = newNode;
  newNode->_prev = tail;
  newNode->_next = _head;
  _head->_prev = newNode;
}

这时候通过调试,就可以确认链表创建并尾插成功:
在这里插入图片描述

二. list 迭代器的实现

list 的重点就是迭代器的实现。
之前的 vectorstring 由于是顺序存储结构,所有迭代器是原生指针,通过 ++ 等操作可以直接访问到对应元素。
但是,list 是链式存储结构,在底层各结点的位置不是连续的,单纯使用原生指针的加减是访问不到结点数据的

那么,怎么样才可以通过 iterator++ 等操作来实现访问结点的效果呢?
得益于C++自定义类型可以进行运算符重载,但Node* 是内置类型,不可以进行运算符重载, 可以将Node*进行封装,再重载 ++ 等操作

1. 迭代器的结构

template<class T>
struct __list_iterator{
  typedef ListNode<T> Node; // 重命名
  Node* _node;  // 迭代器指向的结点指针

  // construct
  __list_iterator(Node* node = nullptr)
  : _node(node)
  {}
};

2. ++,–,*,==,!=

接着是实现 ++,--,* 操作符的重载
++ 使迭代器指向当前结点的后驱
-- 使迭代器指向当前结点的前驱
* 得到结点的数据

typedef __list_iterator<T> self;  // 重命名
self &operator++()
{
  _node = _node->_next;
  return *this;
}

self operator++(int)
{
  self tmp(*this);
  _node = _node->_next;
  return tmp;
}

self &operator--()
{
  _node = _node->_prev;
  return *this;
}

self operator--(int)
{
  self tmp(*this);
  _node = _node->_prev;
  return tmp;
}

T& operator*()
{
  return _node->_data;
}

bool operator!=(const self &s)
{
  return _node != s._node;
}

bool operator==(const self &s)
{
  return _node == s._node;
}

list 类中添加 end, begin

typedef __list_iterator<T> iterator;
iterator begin()
{
  return _head->_next;
}
iterator end()
{
  return _head;
}
const_iterator begin() const
{
  return _head->_next;
}
const_iterator end() const
{
  return _head;
}

随后进行测试,迭代器构建成功:
在这里插入图片描述

3. ->

若结点的数据域的类型是自定义类型,例如下面的自定义类型 AA

struct AA{
  int _a1;
  int _a2;
};

当然可以先对迭代器进行解引用, 再访问成员:(*it)._a1
或者直接使用箭头进行访问: it->_a1

这里直接给出 operator->()的实现

T* operator->()
{
  return &_node->data;
}

这样的实现方式会令人感到奇怪,为什么是直接返回结点的数据域地址呢?

这里类似于运算符重载中的后置++——将int放入参数括号内,也是一种特殊处理。
当出现迭代器后面跟了一个->时,C++语法规定省略了一个->, 实际上为 it.operator->()->_a1,这样就可以理解为什么返回的是结点的数据域地址了。

为了实现逻辑自恰,对此进行特殊处理。

4. 利用模板实现const迭代器

const迭代器和普通迭代器的区别是能否对迭代器指向的数据进行修改,不是直接简单粗暴的 cosnt iterator ,迭代器本身是需要改变的。

那么两者有区别的就是 operator*()operator->() 的返回类型。
普通迭代器是:T& operator*(),T* operator->()
const迭代器:const T& operator*(),const T* operator->()

既然两者十分相似,就没有必要在重新创建一个 __const_list_iterator 这样的类,导致代码冗余,重复。
模板这个时候又发挥了作用
可以直接在迭代器的类模板再添加两个类型,在重命名迭代器的时候只要放入对应的类型,让编译器进行类型推演即可

template<class T, class Ref, class Ptr>
class __list_iterator{
  //...
};

template<class T>
class list{
public:
  // 重命名
  typedef __list_iterator<T, T&, T*> iterator;  
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  //...
};

三. 完整代码

list 的其他接口实现就不过多赘述,这里直接贴上模拟实现 list 的完整代码

list.h

#ifndef __LIST_H__
#define __LIST_H__
#include <assert.h>

namespace wr
{
  // ListNode
  template <typename T>
  struct ListNode
  {
    ListNode<T> *_next; // 指向后继结点的指针
    ListNode<T> *_prev; // 指向前驱结点的指针
    T _data;            // 存放结点的数据

    ListNode(const T &val = T()) // 全缺省构造
        : _next(nullptr), _prev(nullptr), _data(val)
    {
    }
  };

  // __list_iterator
  template <typename T, typename Ref, typename Ptr>
  struct __list_iterator
  {
    typedef ListNode<T> Node;
    typedef __list_iterator<T, Ref, Ptr> self; // 重命名为self

    Node *_node; // 迭代器指向的结点指针

    __list_iterator(Node *node = nullptr)
        : _node(node)
    {
    }
    __list_iterator(const self &s)
        : _node(s._node)
    {
    }

    self &operator++()
    {
      _node = _node->_next;
      return *this;
    }
    self operator++(int)
    {
      self tmp(*this);
      _node = _node->_next;
      return tmp;
    }
    self &operator--()
    {
      _node = _node->_prev;
      return *this;
    }
    self operator--(int)
    {
      self tmp(*this);
      _node = _node->_prev;
      return tmp;
    }
    Ref operator*()
    {
      return _node->_data;
    }
    Ptr operator->()
    {
      return &(operator*());
    }

    bool operator!=(const self &s)
    {
      return _node != s._node;
    }
    bool operator==(const self &s)
    {
      return _node == s._node;
    }
  };

  // list
  template <typename T>
  class list
  {
    typedef ListNode<T> Node;

  public:
    typedef __list_iterator<T, T &, T *> iterator;
    typedef __list_iterator<T, const T &, const T *> const_iterator;

    list()
    {
      empty_init();
    }
    list(int n, const T &val = T())
    {
      empty_init();
      for (int i = 0; i < n; ++i)
      {
        push_back(val);
      }
    }
    template<typename InputIterator>
    list(InputIterator first, InputIterator last)
    {
      empty_init();
      while (first != last)
      {
        push_back(*first);
        ++first;
      }
    }
    list(const list<T> & l)
    {
      empty_init();
      for (const auto &e : l)
      {
        push_back(e);
      }
    }
    list<T>& operator=(const list<T> l)
    {
      swap(l);
      return *this;
    }
    ~list()
    {
      clear();
      delete _head;
      _head = nullptr;
    }

    
    // List Iterator
    iterator begin()
    {
      return _head->_next;
    }
    iterator end()
    {
      return _head;
    }
    const_iterator begin() const
    {
      return _head->_next;
    }
    const_iterator end() const
    {
      return _head;
    }
    
    
    // List Capacity
    size_t size() const
    {
      size_t size = 0;
      const_iterator it = begin();
      while (it != end())
      {
        ++size;
        ++it;
      }
      return size;
    }
    bool empty() const
    {
      return !size();
    }

    
    // List Access
    T &front()
    {
      return *(begin());
    }
    const T &front() const
    {
      return *(begin());
    }
    T &back()
    {
      return *(--end());
    }
    const T &back() const
    {
      return *(--end());
    }

    
    // List Modify
    void push_back(const T &val = T())
    {
      // Node *newNode = new Node(val);
      // Node *tail = _head->_prev;

      // tail->_next = newNode;
      // newNode->_prev = tail;
      // newNode->_next = _head;
      // _head->_prev = newNode;
      insert(end(), val);
    }
    void pop_back()
    {
      erase(--end());
    }
    void push_front(const T &val = T())
    {
      insert(begin(), val);
    }
    void pop_front()
    {
      erase(begin());
    }
    iterator insert(iterator pos, const T &val)
    { // 在pos位置前插入值为val的节点
      Node *cur = pos._node;
      Node *prev = cur->_prev;
      Node *newNode = new Node(val);

      prev->_next = newNode;
      newNode->_prev = prev;
      newNode->_next = cur;
      cur->_prev = newNode;

      return newNode;
    }
    iterator erase(iterator pos)
    { // 删除pos位置的节点,返回该节点的下一个位置
      assert(pos != end());

      Node *cur = pos._node;
      Node *prev = cur->_prev;
      Node *next = cur->_next;
      prev->_next = next;
      next->_prev = prev;

      delete cur;

      return next;
    }
    void clear()
    {
      iterator it = begin();
      while (it != end())
      {
        it = erase(it);
      }
    }
    void swap(list<T> &l)
    {
      std::swap(_head, l._head);
    }

  private:
    void empty_init()
    {
      _head = new Node;
      _head->_next = _head;
      _head->_prev = _head;
    }
    Node *_head;
  };
}

#endif // __LIST_H__

test.cpp

#include <iostream>
#include <utility>
#include <string>
#include "list.h"

using namespace std;
using namespace wr;

#define SHOW(x)       \
  for (auto e : x)    \
    cout << e << " "; \
  cout << endl;       \

void Test1()
{
  list<int> l;
  l.push_back(1);
  l.push_back(2);
  l.push_back(3);
  l.push_back(4);
  l.push_back(5);

  list<int>::iterator lt = l.begin();
  while (lt != l.end())
  {
    cout << *lt << " ";
    lt++;
  }
  cout << endl;
}

void Test2()
{
  list<int> l;
  l.push_back(1);
  l.push_back(2);
  l.push_back(3);
  l.push_back(4);
  l.push_back(5);
  l.push_back(6);
  SHOW(l);

  l.push_front(0);
  SHOW(l);
  l.pop_back();
  SHOW(l);
  l.pop_front();
  SHOW(l);
  l.clear();
  SHOW(l);
}

void Test3()
{
  list<string> l1;
  l1.push_back("1111");
  l1.push_back("2222");
  l1.push_back("3333");
  l1.push_back("4444");
  l1.push_back("5555");
  l1.push_back("6666");
  SHOW(l1);

  list<string> l2;
  l2.push_back("aaaa");
  l2.push_back("bbbb");
  l2.push_back("cccc");
  l2.push_back("dddd");
  l2.push_back("eeee");
  SHOW(l2);

  l1.swap(l2);
  SHOW(l1);

  l1.front() = "1111";
  l1.back() = "9999";
  cout << l1.front() << endl;
  cout << l1.back() << endl;
  SHOW(l1);
}

void Test4()
{
  list<int> l;
  cout << l.empty() << endl;
  cout << l.size() << endl;

  l.push_back(1);
  l.push_back(1);
  l.push_back(1);
  l.push_back(1);
  l.push_back(1);
  l.push_back(1);
  l.push_back(1);
  cout << l.empty() << endl;
  cout << l.size() << endl;
}

void Test5()
{
  char a[] = "abcdeftg";
  list<char> l1(a, a + sizeof(a) / sizeof(char));
  SHOW(l1);
  cout << l1.size() << endl;

  list<char> l2(l1);
  SHOW(l2);

  list<char> l3;
  l3.push_back('1');
  l2.swap(l3);
  SHOW(l2);
  SHOW(l3);
}

int main()
{
  Test1();
  //Test2();
  //Test3();
  //Test4();
  //Test5();

  return 0;
}

本章完。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/413691.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新火种AI|微软扶持下一个OpenAI?Mistral AI新模型对标GPT-4,上线即挤爆

作者&#xff1a;一号 编辑&#xff1a;美美 OpenAI的大金主微软&#xff0c;还想缔造“下一个OpenAI”。 周一晚间&#xff0c;成立仅9个月的Mistral AI正式发布了最强力的旗舰模型Mistral Large。和此前他们所推出的一系列模型不同&#xff0c;Mistral AI本次发布的版本性…

c语言经典测试题7

1.题1 int main() {int count 0;int x -1;while (x){count;x x >> 1;}printf("%d", count);return 0; } 上述代码的运行结果是什么呢&#xff1f; 我们来分析一下&#xff1a;我们知道在vs中右移操作符的规则是&#xff0c;右边抛弃&#xff0c;左边由符…

什么是智能合约

前言&#xff1a;在介绍智能合约的前提下&#xff0c;需要先介绍一下区块链 一.什么是区块链 区块链实质上是一个去中心化、分布式的可进行交易的数据库或账本&#xff0c;具有下列典型特征&#xff1a; 去中心化&#xff1a;简单来说&#xff0c;在网络上一个或多个服务器瘫…

nn.Linear() 使用提醒

原本以为它是和nn.Conv2d()一样&#xff0c;就看第二个维度的数值&#xff0c;今天才知道&#xff0c;它是只看最后一个维度的数值&#xff01;&#xff01;&#xff01; 例子1 Descripttion: Result: Author: Philo Date: 2024-02-27 14:33:50 LastEditors: Philo LastEditT…

GCN,R-GCN,岭回归,SVR,随机森林,Adaboost

图卷积神经网络(graph convolutional network, GCN),它将卷积神经网络拓展到图结构形式 中&#xff0c;GCN因可以很好地融合图结构数据的结构特征和属性特征并且有较好的组合泛化能力而被广泛使用。 关系图卷积神经网络(relational-graph convolutional network, R-GCN)&#…

排序算法2:选择排序、堆排序、归并排序

选择排序&#xff1a;简单选择排序、堆排序 一、简单选择排序 #include <stdio.h> #include <stdlib.h> #include <time.h> typedef int ElemType; typedef struct{ElemType *elem; //整形指针,申请的堆空间的起始地址存入elem int TableLen; //存储动态数组…

【办公类-21-05】20240227单个word按“段落数”拆分多个Word(成果汇编 只有段落文字 1拆5)

作品展示 背景需求 前文对一套带有段落文字和表格的word进行13份拆分 【办公类-21-04】20240227单个word按“段落数”拆分多个Word&#xff08;三级育婴师操作参考题目1拆13份&#xff09;-CSDN博客文章浏览阅读293次&#xff0c;点赞8次&#xff0c;收藏3次。【办公类-21-04…

jmeter(四)HTTP请求

启动jmeter&#xff0c;建立一个测试计划 这里再次说说怎么安装和启动jmeter吧&#xff0c;昨天下午又被人问到怎样安装和使用&#xff0c;我也是醉了&#xff1b;在我看来&#xff0c;百度能解决百分之八十的问题&#xff0c;特别是基础的问题。。。 安装&#xff1a;去官网…

云服务器ECS价格表出炉_2024年最新价格表——阿里云

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

Idea报错

在处理项目中Idea报错&#xff1a; 问题1&#xff1a; Error starting ApplicationContext. To display the conditions report re-run your application with debug enabled. 2024-02-27 17:16:54.427 ERROR 11472 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisRepo…

京东数据分析(电商数据分析):2024年1月京东白酒TOP10品牌销量销额排行榜

在公布2024年1月京东白酒品牌排行榜之前&#xff0c;分享一个有点意思的现象&#xff1a;在今年龙年春晚“黄金5分钟”的广告片里&#xff0c;白酒局知名的品牌基本都亮相了&#xff08;茅台、五粮液、洋河股份、郎酒、古井贡酒、水井坊&#xff09;&#xff0c;但今年汾酒却缺…

4核8G服务器多少钱?腾讯云和阿里云哪家便宜?

4核8G云服务器多少钱一年&#xff1f;阿里云ECS服务器u1价格955.58元一年&#xff0c;腾讯云轻量4核8G12M带宽价格是646元15个月&#xff0c;阿腾云atengyun.com整理4核8G云服务器价格表&#xff0c;包括一年费用和1个月收费明细&#xff1a; 云服务器4核8G配置收费价格 阿里…

Qt RGB三色灯上位机

今天突发奇想做一个Qt RGB三色灯上位机&#xff0c;刚好手上有一个RGB三色灯和arduion开发板。 我是想实现一个颜色选择器界面然后鼠标点击颜色区域就可以发出rgb的值&#xff0c;然后把这个值通过串口线发送给arduion,arduion再解析出数据发送给RGB三色灯。 实现界面如下&…

代码随想录算法训练营day24

题目&#xff1a;77. 组合 参考链接&#xff1a;代码随想录 回溯法理论基础 回溯三部曲&#xff1a;回溯函数模板返回值以及参数、回溯函数终止条件、回溯搜索的遍历过程。 模板框架&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的生活垃圾检测与分类系统(Python+PySide6界面+训练代码)

摘要&#xff1a;本篇博客详细讲述了如何利用深度学习构建一个生活垃圾检测与分类系统&#xff0c;并且提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并进行了与前代算法YOLOv7、YOLOv6、YOLOv5的细致对比&#xff0c;展示了其在图像、视频、实时视频流和批量…

人工智能之Tensorflow程序结构

TensorFlow作为分布式机器学习平台&#xff0c;主要架构如下&#xff1a; 网络层&#xff1a;远程过程调用(gRPC)和远程直接数据存取(RDMA)作为网络层&#xff0c;主要负责传递神经网络算法参数。 设备层&#xff1a;CPU、GPU等设备&#xff0c;主要负责神经网络算法中具体的运…

IDEA基础——创建Maven项目卡在导入Maven依赖项的解决方案

解决方案 方案一&#xff1a;添加阿里云maven镜像源&#xff08;推荐&#xff09;1. 找到你maven的用户配置文件路径&#xff0c;一般为maven仓库路径的父路径&#xff1a;./xxx/repository的上一个目录2. 在配置文件中添加阿里云镜像&#xff1a; 方案二&#xff1a;下载模板配…

【MySQL】DCL

DCL英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访问权限。 1. 管理用户 在MySQL数据库中&#xff0c;DCL&#xff08;数据控制语言&#xff09;是用来管理用户和权限的语句集合。通过DCL语句&#xff0c;可以创建、修改、删…

word中的表格跨页了,要如何维持每一页的表头都有标题

在制作 Word 的表格时&#xff0c;因为内容很长&#xff0c;会一直往下延伸&#xff0c; 不过因为是混合内容&#xff0c;也不适合用 Excel 来制作表格&#xff0c;而在延伸表格时有个问题&#xff0c;当表格遇到跨页时&#xff0c;跨页后的第一行是不会像第一页打好的标题列显…

Springboot 多级缓存设计与实现

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…