【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现

简介: 【C++练级之路】【Lv.10】【STL】priority_queue类和反向迭代器的模拟实现

一、仿函数

1.1 仿函数的介绍

仿函数,是一种特殊类型的类,它重载了()运算符,使得这个类的使用看起来像一个函数,因此它又称为函数对象


具体来说,仿函数就是将函数的特性赋予到类上,使得这个类有了类似函数的行为。

1.2 仿函数的优势

C++设计仿函数之初,其实就是想替代庞杂难懂的函数指针,将函数指针替换为简单易懂的仿函数。


这里列举两个常用的仿函数——less和greater

template<class T>
struct less
{
  bool operator()(const T& x, const T& y)
  {
    return x < y;
  }
};

template<class T>
struct greater
{
  bool operator()(const T& x, const T& y)
  {
    return x > y;
  }
};

二、priority_queue

细节:

  1. priority_queue也是容器适配器,默认容器使用vector
  2. 其底层数据结构是,并且默认情况为大堆
    如果不了解堆,可以先看往期【数据结构】【版本2.0】【树形深渊】——二叉树入侵
  3. 为了能方便调整大小堆,增加了仿函数的模板
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
private:
  Container _con;
};

悄悄说一句:其实容器模板和仿函数模板位置互换,才更加合理!(平时不怎么会换默认容器,但是会经常换仿函数来控制大小堆)

2.1 push

入堆

细节:

  1. 先尾插元素
  2. 再使用向上调整算法
void push(const T& x)
{
  _con.push_back(x);
  adjust_up(_con.size() - 1);
}

向上调整算法

细节:

  • 构造一个仿函数模板对象,再利用重载的()运算符进行比较(当然,也可以使用匿名对象)
void adjust_up(int child)
{
  Compare com;
  int parent = (child - 1) / 2;
  while (child > 0)
  {
    if (com(_con[parent], _con[child]))
    {
      swap(_con[parent], _con[child]);
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}

2.2 pop

出堆

细节:

  1. 先首尾元素互换
  2. 再尾删元素
  3. 最后使用向下调整算法
void pop()
{
  swap(_con[0], _con[_con.size() - 1]);
  _con.pop_back();
  adjust_down(0);
}

向下调整算法

细节:

  • 构造一个仿函数模板对象,再利用重载的()运算符进行比较(当然,也可以使用匿名对象)
void adjust_down(int parent)
{
  Compare com;
  int child = parent * 2 + 1;
  while (child < _con.size())
  {
    if (child + 1 < _con.size() && com(_con[child], _con[child+1]))
    {
      ++child;
    }

    if (com(_con[parent], _con[child]))
    {
      swap(_con[parent], _con[child]);
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}

2.3 top

获取堆顶元素

const T& top() const
{
  return _con[0];
}

2.4 size

获取堆中有效元素个数

size_t size() const
{
  return _con.size();
}

2.5 empty

判断堆是否为空

bool empty() const
{
  return _con.empty();
}

三、反向迭代器

其实,反向迭代器也是一种适配器,它是根据不同容器的正向迭代器,来生成对应的反向迭代器。


同时,反向迭代器追求一种对称美,rbegin()在end(),rend()在begin()。

3.1 成员变量与默认成员函数

细节:

  1. 仍然使用struct,标明公有属性
  2. 成员变量是一个正向迭代器
  3. 提供带参构造函数(其余的默认成员函数不用显式定义,浅拷贝即可)
template<class Iterator, class Ref, class Ptr>
struct __reverse_iterator
{
  typedef __reverse_iterator self;
  Iterator _cur;

  __reverse_iterator(Iterator it)
    : _cur(it)
  {}
};

3.2 operator*

细节:

  1. 迭代器先自减,再解引用返回
  2. 返回引用,为了区别普通迭代器和const迭代器
Ref operator*()
{
  Iterator tmp = _cur;
  return *--tmp;
}

3.3 operator->

细节:

  1. 直接调用operator*(),根据不同容器的数据取地址返回
  2. 返回指针,为了区别普通迭代器和const迭代器
Ptr operator->()
{
  return &(operator*());
}

3.4 operator++

细节:

  1. 反向迭代器的++,就是正向迭代器的- -
  2. 为了区分前置和后置,后置参数加上int(无实际意义,以示区分)
  3. 前置传引用返回,后置传值返回
self& operator++()
{
  --_cur;
  return *this;
}

self operator++(int)
{
  Iterator tmp = _cur;
  --_cur;
  return tmp;
}

3.5 operator- -

细节:同上

self& operator--()
{
  ++_cur;
  return *this;
}

self operator--(int)
{
  Iterator tmp = _cur;
  ++_cur;
  return tmp;
}

3.6 relational operators

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

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

四、反向迭代器的适用

4.1 vector

template<class T>
class vector
{
public:
  typedef T* iterator;
  typedef const T* const_iterator;

  typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
  typedef __reverse_iterator<iterator, const T&, const T*> const_reverse_iterator;

  iterator begin()
  {
    return _start;
  }

  iterator end()
  {
    return _finish;
  }

  const_iterator begin() const
  {
    return _start;
  }

  const_iterator end() const
  {
    return _finish;
  }
  //...
}

4.1.1 rbegin

reverse_iterator rbegin()
{
  return reverse_iterator(end());
}

const_reverse_iterator rbegin() const
{
  return const_reverse_iterator(end());
}

4.1.2 rend

reverse_iterator rend()
{
  return reverse_iterator(begin());
}

const_reverse_iterator rend() const
{
  return const_reverse_iterator(begin());
}

4.2 list

template<class T>
class list
{
public:
  typedef __list_node<T> node;
  typedef __list_iterator<T, T&, T*> iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;

  typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
  typedef __reverse_iterator<iterator, const T&, const T*> const_reverse_iterator;

  iterator begin()
  {
    return iterator(_head->_next);
  }

  const_iterator begin() const
  {
    return const_iterator(_head->_next);
  }

  iterator end()
  {
    return iterator(_head);
  }

  const_iterator end() const
  {
    return const_iterator(_head);
  }
  //...
}

4.2.1 rbegin

reverse_iterator rbegin()
{
  return reverse_iterator(end());
}

const_reverse_iterator rbegin() const
{
  return const_reverse_iterator(end());
}

4.2.2 rend

reverse_iterator rend()
{
  return reverse_iterator(begin());
}

const_reverse_iterator rend() const
{
  return const_reverse_iterator(begin());
}

总结

这次学习了仿函数的概念和基本用法,对于升降序、大小堆等转换具有极大便利。同时实现了新的容器适配器——priority_queue(优先级队列),实际上就是堆。并且也完美实现了同为适配器的反向迭代器,至此,对于适配器有了更深一步的了解和运用。


真诚点赞,手有余香


相关文章
|
24天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
2天前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
31 16
|
6天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
38 6
|
24天前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
15天前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
52 1
|
24天前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
24天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
24天前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
26天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
2月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
82 19