C++中的stack和queue

C++中的stack和queue

前言

这一节的内容对于stack和queue的使用介绍会比较少,主要是因为stack和queue的使用十分简单,而且他们的功能主要也是在做题的时候才会显现。这一栏目暂时不会写关于做题的内容,后续我会额外开一个做题日记的栏目的。

这节内容主要给大家讲解一下stack和queue的特殊性,和相关实现。

使用

在这里插入图片描述

对stack和queue的简单介绍

一、基本概念
  1. Stack(栈)

    • 后进先出(LIFO):最后插入的元素最先被移除。
    • 类比:一摞盘子(只能从顶部取放)。
    • 核心操作:push(入栈)、pop(出栈)、top(访问栈顶)。
  2. Queue(队列)

    • 先进先出(FIFO):最先插入的元素最先被移除。
    • 类比:排队购票(队尾进,队头出)。
    • 核心操作:push(入队)、pop(出队)、front/back(访问队头/队尾)。

二、底层实现原理//重点
  • 容器适配器
    stackqueue 不是独立的容器,而是基于其他容器(如 dequelist)的接口封装。

    // 默认底层容器:deque(双端队列)//这个容器会在下一节讲解
    stack<int> s;          // 等价于 stack<int, deque<int>>
    queue<int> q;          // 等价于 queue<int, deque<int>>// 自定义底层容器(需满足操作要求)
    stack<int, vector<int>> s_vec;   // 使用 vector
    queue<int, list<int>> q_list;    // 使用 list
    

在这里插入图片描述

  • 底层容器要求

    操作stack 所需支持queue 所需支持
    尾部插入push_back()push_back()
    尾部删除pop_back()
    头部删除pop_front()
    访问尾部back()back()
    访问头部front()

    ⚠️ 注意vector 不能直接用于 queue(缺少 pop_front())。


三、核心操作与时间复杂度
操作stackqueue时间复杂度
插入元素push(val)push(val)O(1)
删除元素pop()pop()O(1)
访问顶部/头部元素top()front()O(1)
访问尾部元素back()O(1)
检查是否为空empty()empty()O(1)
获取元素数量size()size()O(1)

💡 为什么高效?
默认基于 deque(动态数组分块存储),插入/删除操作均摊常数时间。


四、使用示例
  1. Stack 示例:括号匹配

    #include <stack>
    bool isValid(string s) {stack<char> st;for (char c : s) {if (c == '(' || c == '[') st.push(c);else {if (st.empty()) return false;char top = st.top();if ((c == ')' && top != '(') || (c == ']' && top != '[')) return false;st.pop();}}return st.empty();
    }
    
  2. Queue 示例:BFS 算法框架

    #include <queue>
    void BFS(Node* start) {queue<Node*> q;q.push(start);while (!q.empty()) {Node* cur = q.front();q.pop();// 处理当前节点for (Node* neighbor : cur->neighbors) {q.push(neighbor);}}
    }
    

五、应用场景
数据结构典型应用场景
stack函数调用栈、表达式求值、DFS 遍历、撤销操作(Ctrl+Z)
queue消息队列、BFS 遍历、缓冲区管理(如打印机任务)、多线程任务调度

六、注意事项
  1. 空容器操作禁止

    stack<int> s;
    s.pop();   // 未定义行为(崩溃)!
    if (!s.empty()) s.pop(); // 正确做法
    
  2. 不支持遍历
    二者均没有迭代器,无法用 for (auto x : s) 遍历。

  3. 底层容器选择

    • stack 优先选 vector(内存局部性好)。
    • queue 只能选 dequelist(避免用 vector)。
  4. 线程安全
    STL 的 stack/queue 不是线程安全的,多线程需手动加锁。


七、进阶技巧
  1. vector 模拟栈(简化内存管理):

    vector<int> v_stack;
    v_stack.push_back(10);      // push
    int top = v_stack.back();   // top
    v_stack.pop_back();         // pop
    
  2. 双栈实现队列

    class Queue {stack<int> in, out;void push(int x) { in.push(x); }int pop() {if (out.empty()) {while (!in.empty()) {out.push(in.top()); in.pop();}}int val = out.top();out.pop();return val;}
    };
    

总结
特性stackqueue
数据顺序LIFO(后进先出)FIFO(先进先出)
核心接口push(), pop(), top()push(), pop(), front()
底层依赖deque/vector/listdeque/list
遍历不支持不支持
适用场景逆序操作、递归顺序处理、缓冲

选择原则

  • 需要回溯操作 → stack(如 DFS、撤销机制)
  • 需要公平调度 → queue(如 BFS、消息队列)

C++ priority_queue (优先队列)详解

一、本质与核心概念

优先队列(priority_queue) 是一种特殊的队列,元素按优先级顺序出队(而非插入顺序)。它是基于堆数据结构(通常是二叉堆)实现的容器适配器。

也就是说,当我们需要使用到这个数据结构的时候,使用这个优先级队列就ok了,它也是被包括在queue头文件中的。

关键特性:
  • 队首元素总是当前队列中优先级最高的元素
  • 默认行为:最大元素优先(大顶堆//堆顶元素最大)
  • 底层结构:默认使用 vector 作为容器//这个优先级队列也是一个容器适配器
  • 时间复杂度
    • 访问队首:O(1)
    • 插入元素:O(log n)
    • 删除队首:O(log n)
二、底层实现原理
template<class T,class Container = std::vector<T>,class Compare = std::less<typename Container::value_type>
> class priority_queue;
  1. 堆结构维护

    • 插入时 (push):将元素添加到末尾,然后进行上浮操作(sift up)//也就是我们学习堆数据结构时候的向上调整。
    • 删除时 (pop):交换首尾元素,删除尾部,然后进行下沉操作(sift down)//也就是我们学习堆数据结构时候的向下调整。
  2. 堆算法函数(底层实际调用):

    std::make_heap()   // 建堆
    std::push_heap()   // 插入后调整
    std::pop_heap()    // 删除前调整
    
三、模板参数详解
参数说明默认值
T元素类型必填
Container底层容器vector<T>
Compare优先级比较函数less<T>
底层容器要求:
  • 必须支持随机访问迭代器(vector/deque
  • 必须支持 front(), push_back(), pop_back()
  • 不兼容 list(缺少随机访问)
四、核心操作与时间复杂度
操作函数时间复杂度说明
插入元素push(const T& val)O(log n)添加元素并调整堆
删除队首pop()O(log n)移除最高优先级元素
访问队首top()O(1)返回队首元素(只读)
检查空empty()O(1)判断是否为空
获取大小size()O(1)返回元素数量
五、创建与初始化
// 默认创建(大顶堆)
std::priority_queue<int> max_heap;// 预分配空间
std::vector<int> vec{3,1,4,1,5};
std::priority_queue<int> pre_heap(vec.begin(), vec.end());// 自定义比较函数(小顶堆)
auto cmp = [](int a, int b) { return a > b; };
std::priority_queue<int, std::vector<int>, decltype(cmp)> min_heap(cmp);// 使用函数对象(推荐)//这其实就是仿函数,等会后面会讲
struct Compare {bool operator()(const Person& a, const Person& b) {return a.age < b.age; // 年龄大的优先}
};
std::priority_queue<Person, std::vector<Person>, Compare> age_heap;
六、自定义优先级规则(关键技巧)
// 方法1:重载 operator<
struct Task {int priority;std::string name;bool operator<(const Task& other) const {return priority < other.priority; // 大顶堆要求}
};// 方法2:自定义函数对象
struct TaskComparator {bool operator()(const Task& a, const Task& b) {return a.priority < b.priority; // 注意:返回true表示a优先级低于b}
};// 方法3:使用标准函数对象(创建小顶堆)
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;

⚠️ 比较函数重要规则

  • comp(a, b) == true 时,a 的优先级将低于 b
  • 对于大顶堆:return a < b;
  • 对于小顶堆:return a > b;
七、典型应用场景
  1. 任务调度系统

    struct Task {int priority;std::function<void()> job;bool operator<(const Task& t) const { return priority < t.priority; }
    };
    std::priority_queue<Task> scheduler;
    
  2. Dijkstra 最短路径算法

    std::priority_queue<std::pair<int, int>> pq; // { -distance, node }
    pq.push({0, start}); // 使用负值实现最小堆
    
  3. 合并K个有序链表

    auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; 
    };
    std::priority_queue<ListNode*, std::vector<ListNode*>, decltype(cmp)> pq(cmp);
    
  4. 实时获取中位数(双堆法):

    std::priority_queue<int> max_heap; // 存储较小一半
    std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap; // 存储较大一半
    
八、底层容器选择策略
容器随机访问内存连续性适用性推荐场景
vector推荐通用场景
deque❌(分块)可用避免尾部扩容
list不兼容-

为什么用 vector 不用 deque

  • vector 内存连续,缓存友好
  • 堆操作主要依赖随机访问,vector 性能更优
九、特殊操作与技巧
  1. 高效初始化

    std::vector<int> data{3,1,4,5,9,2,6};
    // 直接通过迭代器建堆(O(n) 时间复杂度)
    std::priority_queue<int> pq(data.begin(), data.end());
    
  2. 清空队列的陷阱

    // 错误方式(低效):
    while (!pq.empty()) pq.pop(); // O(n log n)// 正确方式:
    std::priority_queue<int> empty;
    std::swap(pq, empty); // O(1)
    
  3. 修改队首元素(高级技巧)

    // 1. 取出队首
    auto top = pq.top();
    pq.pop();// 2. 修改元素
    top.value = new_value;// 3. 重新插入
    pq.push(top);
    
十、与相似数据结构的对比
特性priority_queueset/multisetmap/multimap
排序方式堆序红黑树序红黑树序
重复元素允许multiset允许multimap允许
随机访问仅队首不支持不支持
插入删除O(log n)O(log n)O(log n)
查找元素不支持O(log n)O(log n)
内存开销高(指针)高(指针)
十一、性能优化指南
  1. 元素使用移动语义

    struct HeavyObject {std::vector<int> big_data;HeavyObject(HeavyObject&& other) noexcept = default;
    };
    pq.push(std::move(obj)); // 避免大对象复制
    
  2. 预留空间(仅 vector 底层)

    // 直接访问底层容器(不推荐但有效)
    std::priority_queue<int, std::vector<int>> pq;
    pq.c.reserve(1000); // 访问底层容器的reserve
    
  3. 自定义内存分配器

    template<typename T>
    class CustomAllocator { /* ... */ };using CustomPQ = std::priority_queue<int, std::vector<int, CustomAllocator<int>>;
    
十二、常见错误与陷阱
  1. 错误比较函数

    // 错误:返回true表示a优先级低于b
    auto cmp = [](int a, int b) { return a < b; }; // 正确的大顶堆
    auto wrong_cmp = [](int a, int b) { return a > b; }; // 错误!实际创建了小顶堆
    
  2. 修改队首破坏堆结构

    // 错误:直接修改队首元素
    pq.top().priority = 100; // 破坏堆性质!
    
  3. 遍历队列陷阱

    while (!pq.empty()) {process(pq.top()); // 错误:忘记pop()导致死循环pq.pop(); // 必须显式pop
    }
    
十三、最佳实践总结
场景推荐方案
需要最高优先级元素priority_queue
需要完整排序setmap
需要遍历元素vector + std::sort
需要快速查找unordered_map
内存敏感场景priority_queue + vector

黄金法则

  1. 优先使用 vector 作为底层容器
  2. 比较函数中明确优先级规则
  3. 避免直接修改队列内部元素
  4. 批量初始化优于逐个插入
  5. 使用移动语义减少拷贝开销
十四、C++20 新特性
  1. 范围构造支持

    std::vector data{5, 3, 8, 1};
    std::priority_queue pq(std::less{}, data); // C++20 类型推导
    
  2. 透明比较器优化

    std::priority_queue<std::string, std::vector<std::string>,std::less<>> pq; // 支持异构查找
    pq.push("test");
    auto it = find_if(begin(pq.c), end(pq.c), [](auto& s){ return s == "test"; }); // 直接比较
    
十五、综合示例:医院急诊系统
struct Patient {std::string name;int severity; // 1-10, 10为最严重time_t arrival_time;bool operator<(const Patient& other) const {// 优先级规则:先按严重程度,再按到达时间return std::tie(severity, arrival_time) < std::tie(other.severity, other.arrival_time);}
};std::priority_queue<Patient> emergency_queue;void admit_patient(std::string name, int severity) {emergency_queue.push({name, severity, time(nullptr)});
}void treat_next_patient() {if (emergency_queue.empty()) return;Patient next = emergency_queue.top();emergency_queue.pop();std::cout << "Treating: " << next.name << " (Severity: " << next.severity << ")\n";
}
总结:优先队列三要素
  1. 数据结构核心:二叉堆实现
  2. 优先级控制:通过比较函数定制
  3. 性能平衡
    • O(1) 访问队首
    • O(log n) 插入/删除
    • O(n) 建堆

📌 使用决策树
需要不断获取 最大值/最小值 + 频繁插入删除 → 选择 priority_queue
需要完整排序/遍历 → 选择 set 或排序 vector
需要键值关联 → 选择 map

容器适配器

那么我们这一节的重点其实就是这个知识点:容器适配器。弄清楚容器适配器是啥?怎么使用。

一、什么是容器适配器?

容器适配器(Container Adapter)是 STL 中的特殊容器,它们不直接管理数据,而是通过"借用"已有容器(如 deque、vector、list)的功能,提供特定接口来实现特定数据结构行为。

类比解释:

这里我结合stack和现实生活中的样例给大家解释一下:

stack(也就是栈)它的特点不就是后进先出嘛,那么要怎么实现后进先出呢?那其实也很简单,首先栈的插入,也就是栈的push功能,其实就是尾部插入数据嘛。那么后进先出,这里其实涉及到的就是pop删除元素的功能函数嘛,后进先出,也就是pop元素的时候默认调用一个尾部删除pop_back函数。栈的一些其余功能,如取栈顶元素是返回数组末尾元素。之前在讲栈的时候就说过栈其实是一个特殊的线性表,特殊在它的操作(如删除操作就是指定尾部删除数据)。但是这些特殊操作我们也可以调用普通数据结构中的某些函数来实现,就好比如vector就可以完美的转换成stack。之前我们学习数据结构的时候,因为没有模板,所以我们要额外写一份栈的代码,但是现在有了模板之后,我们就可以借助模板,将某些容器转换成某些别的容器,就好比如vector转换成stack。

用生活中的例子给大家解释的话,我觉得大家可以这样想:假设现在电脑上没有网线接口或者有线耳机的线孔,只有一个Type-C接口,那么这个Type-C接口我们就可以把它当作vector。现在网上不是有很多扩展坞嘛,扩展坞上啥孔都有,我们把这个扩展坞想象成模板。假设我现在想给我的笔记本电脑插上网线,但是没有网线接口。那么我们就可以使用扩展坞将原来的Type-C接口变成网线接口。在C++中,vector转换成stack是容器转容器,在现实里,Type-C接口转网线接口是接口转接口。大家可以这样类比的理解,虽然这是一个不太严谨的例子,但是我个人觉得还是不错的。

所以说这里的网线其实是借用Type-C接口进行与电脑的连接。

二、为什么需要容器适配器?
方式直接实现数据结构使用容器适配器
开发成本需从头实现所有操作复用已有容器代码
灵活性固定实现可自由切换底层容器
维护性需单独维护依赖 STL 标准容器
性能优化优化困难轻松切换高效底层容器
三、三大容器适配器
  1. stack:后进先出(LIFO)结构
  2. queue:先进先出(FIFO)结构
  3. priority_queue:优先级队列(元素按优先级排序)
四、底层容器要求(核心知识)

每个适配器对底层容器有特定接口要求:

1. stack 的底层容器必须支持:
push_back()  // 尾部插入
pop_back()   // 尾部删除
back()       // 访问尾部元素

✅ 兼容容器:deque(默认)、vectorlist

2. queue 的底层容器必须支持:
push_back()  // 尾部插入
pop_front()  // 头部删除
front()      // 访问头部元素
back()       // 访问尾部元素(非必须但常用)

✅ 兼容容器:deque(默认)、list
❌ 不兼容:vector(缺少 pop_front)

3. priority_queue 的额外要求:
// 需要随机访问迭代器
front()             // 访问首元素
push_back()         // 尾部插入
pop_back()          // 尾部删除
// 内部使用堆算法(需支持随机访问)

✅ 兼容容器:vector(默认)、deque

五、底层容器性能对比
操作vectordequelist
尾部插入O(1) 均摊O(1)O(1)
头部插入O(n)O(1)O(1)
随机访问O(1)O(1)O(n)
内存布局连续内存分块连续非连续
适配 stack✅ 推荐
适配 queue❌ 不兼容✅ 默认

💡 黄金搭配

  • stack + vector:内存连续,缓存友好
  • queue + deque:高效头尾操作
  • priority_queue + vector:堆算法需要随机访问
六、自定义底层容器(实战示例)
#include <stack>
#include <vector>
#include <list>
#include <queue>// 自定义stack底层容器
std::stack<int, std::vector<int>> vec_stack;  // 基于vector
std::stack<int, std::list<int>> list_stack;    // 基于list// 自定义queue底层容器
std::queue<int, std::list<int>> list_queue;    // 基于list// priority_queue使用vector
std::priority_queue<int, std::vector<int>> max_heap;
七、容器适配器 VS 普通容器
特性普通容器(vector/list)容器适配器(stack/queue)
迭代器✅ 支持完整迭代器❌ 无迭代器
遍历✅ 可随机访问任意元素❌ 只能访问特定端元素
底层数据✅ 可直接访问❌ 隐藏底层实现
功能接口提供完整容器操作仅暴露特定数据结构接口
设计目的通用数据存储特定数据行为抽象
八、实现原理揭秘
template<typename T, typename Container = std::deque<T>>
class stack //栈
{public:void push(const T& value) { c.push_back(value);  // 调用底层容器的push_back}void pop() { c.pop_back();        // 调用底层容器的pop_back}T& top() { return c.back();     // 调用底层容器的back}size_t size(){return c.size();//调用底层容器的size}bool empty(){return c.empty();//调用底层容器的empty}private:Container c;  // 底层容器对象
};
template<class T, class Container = std::deque<T>>class queue//队列{public:void push(const T& x){_con.push_back(x);}void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};
#include <vector>
#include <functional> // 用于 std::less
#include <algorithm> // 用于 std::swaptemplate <typename T, typename Container = std::vector<T>,typename Compare = std::less<typename Container::value_type>>
class priority_queue //优先队列
{
public:// 默认构造函数priority_queue() = default;// 使用自定义比较器的构造函数explicit priority_queue(const Compare& comp) : comp(comp) {}// 通过迭代器范围构造template <typename InputIterator>priority_queue(InputIterator first, InputIterator last, const Compare& comp = Compare()): c(first, last), comp(comp) {build_heap(); // 构建堆}// 插入元素void push(const T& value) {c.push_back(value);       // 添加到末尾sift_up(c.size() - 1);    // 上浮新元素}// 删除堆顶元素void pop() {if (c.empty()) return;std::swap(c[0], c.back()); // 交换首尾元素c.pop_back();              // 删除原堆顶if (!c.empty()) {sift_down(0);          // 下沉新堆顶}}// 访问堆顶元素(只读)const T& top() const {return c.front();}// 元素数量size_t size() const {return c.size();}// 检查是否为空bool empty() const {return c.empty();}private:Container c;     // 底层容器Compare comp;    // 比较函数对象// 构建堆(Floyd算法)void build_heap() {// 从最后一个非叶子节点开始下沉for (int i = (c.size() / 2) - 1; i >= 0; --i) {sift_down(i);}}// 上浮操作void sift_up(size_t idx) {while (idx > 0) {size_t parent = (idx - 1) / 2;// 如果当前节点优先级高于父节点if (comp(c[parent], c[idx])) {std::swap(c[parent], c[idx]);idx = parent;} else {break; // 堆条件已满足}}}// 下沉操作void sift_down(size_t idx) {size_t largest = idx;const size_t n = c.size();while (true) {size_t left = 2 * idx + 1;size_t right = 2 * idx + 2;// 检查左子节点if (left < n && comp(c[largest], c[left])) {largest = left;}// 检查右子节点if (right < n && comp(c[largest], c[right])) {largest = right;}// 如果当前节点不是最大,交换并继续下沉if (largest != idx) {std::swap(c[idx], c[largest]);idx = largest;} else {break; // 堆条件已满足}}}
};

关键点:所有操作转发到底层容器的特定接口

九、特殊行为解析
  1. 为什么 stack 不提供 clear()
    因为标准委员会认为 while(!s.empty()) s.pop() 已足够清晰

  2. priority_queue 的排序控制

    // 创建小顶堆(默认是大顶堆)
    std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
    
  3. queueback() 不是必须的
    某些实现(如早期 STL)允许使用不支持 back() 的容器

十、设计思想与最佳实践
  1. 适配器模式精髓

    • 不改变已有容器代码
    • 通过限制接口实现新功能
    • 符合开闭原则(对扩展开放,对修改封闭)
  2. 使用建议

    // 需要后进先出 → 选择 stack
    stack<int, vector<int>> s;  // 推荐 vector 提高缓存命中率// 需要先进先出 → 选择 queue
    queue<int> q;               // 默认 deque 最均衡// 需要优先级处理 → priority_queue
    priority_queue<int> pq;     // 默认 vector 大顶堆
    
  3. 避免的陷阱

    // 错误:vector 缺少 pop_front()
    std::queue<int, std::vector<int>> invalid_queue; // 错误:尝试遍历适配器
    for(auto it = s.begin(); it != s.end(); ++it) // stack 无迭代器
    
十一、性能优化技巧
  1. 预分配内存(vector 底层时)

    std::stack<int, std::vector<int>> s;
    s.c.reserve(1000);  // 直接访问底层容器预分配
    
  2. 选择分块存储容器避免扩容

    // 处理超大数据时使用 deque
    std::stack<int, std::deque<int>> big_stack;
    
  3. 自定义内存分配器

    std::stack<int, std::vector<int, MyAllocator<int>>> custom_stack;
    
十二、总结:容器适配器的本质
维度说明
身份底层容器的"接口转换器"
核心能力通过限制/转发底层容器接口实现特定数据结构
价值代码复用 + 接口统一 + 灵活切换
哲学思想“不要重复发明轮子”(Don’t Reinvent The Wheel)
典型应用算法中的临时数据结构(DFS/BFS)、消息系统、撤销操作栈

使用口诀
栈用 vector,队列用 deque
优先队列 vector,底层切换要合规
迭代遍历不可为,专用接口记心间!

仿函数

一、什么是仿函数?

仿函数(Function Object)是C++中行为类似函数的对象。它们通过重载函数调用运算符operator()实现,使得对象可以像函数一样被调用。

基本特征:

  • 是类对象,不是函数
  • 重载了operator()运算符
  • 可以保存状态(成员变量)
  • 可以作为参数传递
  • 可以像普通函数一样调用

简单示例:

#include <iostream>// 定义一个加法仿函数
struct Adder {int operator()(int a, int b) const {return a + b;}
};int main() {Adder add; // 创建仿函数对象std::cout << add(3, 4); // 像函数一样调用:输出7return 0;
}

二、为什么需要仿函数?

1. 相比普通函数的优势:

特性普通函数仿函数
状态保持❌ 无状态✅ 可保存状态
内联优化❌ 通常不能✅ 可内联
作为模板参数❌ 不支持✅ 支持
多态行为❌ 不支持✅ 支持

2. 实际应用场景:

  • STL算法的自定义行为(如排序、查找)
  • 回调机制
  • 策略模式实现
  • 函数组合

三、仿函数的类型分类

1. 按参数数量分类:

类型参数数量示例
生成器(Generator)0rand()
一元仿函数(Unary)1negate<T>()
二元仿函数(Binary)2plus<T>()

2. 按行为分类:

类型返回值用途示例
算术仿函数数值数学运算plus, minus
关系仿函数bool比较操作less, greater
逻辑仿函数bool逻辑运算logical_and, logical_or

四、STL内置仿函数详解

STL在<functional>头文件中提供了常用仿函数:

1. 算术仿函数:

#include <functional>std::plus<int> add;
int result = add(5, 3); // 8std::minus<int> subtract;
result = subtract(10, 4); // 6std::multiplies<int> multiply;
result = multiply(3, 4); // 12std::divides<int> divide;
result = divide(10, 2); // 5std::modulus<int> mod;
result = mod(10, 3); // 1std::negate<int> neg;
result = neg(7); // -7

2. 关系仿函数:

std::equal_to<int> equal;
bool isEqual = equal(5, 5); // truestd::not_equal_to<int> notEqual;
isEqual = notEqual(5, 3); // truestd::greater<int> greater;
isEqual = greater(5, 3); // truestd::less<int> less;
isEqual = less(3, 5); // truestd::greater_equal<int> ge;
isEqual = ge(5, 5); // truestd::less_equal<int> le;
isEqual = le(4, 5); // true

3. 逻辑仿函数:

std::logical_and<bool> and_op;
bool res = and_op(true, false); // falsestd::logical_or<bool> or_op;
res = or_op(true, false); // truestd::logical_not<bool> not_op;
res = not_op(true); // false

五、自定义仿函数实现

1. 基本实现:

// 幂运算仿函数
class Power {
public:double operator()(double base, int exponent) const {double result = 1.0;for (int i = 0; i < exponent; ++i) {result *= base;}return result;}
};// 使用
Power power;
double eight = power(2, 3); // 8.0

2. 带状态的仿函数:

// 计数器仿函数
class Counter {int count = 0;
public:int operator()() { return ++count; }void reset() {count = 0;}
};// 使用
Counter c;
c(); // 1
c(); // 2
c.reset();
c(); // 1

3. 模板仿函数:

template <typename T>
class Clamp {T min_val;T max_val;
public:Clamp(T min, T max) : min_val(min), max_val(max) {}T operator()(T value) const {if (value < min_val) return min_val;if (value > max_val) return max_val;return value;}
};// 使用
Clamp<int> intClamp(0, 100);
int value = intClamp(150); // 100Clamp<double> doubleClamp(0.0, 1.0);
double dval = doubleClamp(-0.5); // 0.0

六、仿函数在STL算法中的应用

1. 排序算法:

#include <algorithm>
#include <vector>std::vector<int> numbers = {5, 3, 8, 1, 4};// 升序排序
std::sort(numbers.begin(), numbers.end(), std::less<int>());// 降序排序
std::sort(numbers.begin(), numbers.end(), std::greater<int>());

2. 查找算法:

// 查找第一个大于5的元素
auto it = std::find_if(numbers.begin(), numbers.end(), [](int x) { return x > 5; });// 使用仿函数替代lambda
struct GreaterThan {int threshold;GreaterThan(int t) : threshold(t) {}bool operator()(int x) const { return x > threshold; }
};it = std::find_if(numbers.begin(), numbers.end(), GreaterThan(5));

3. 转换算法:

#include <vector>
#include <cmath>struct SquareRoot {double operator()(double x) const {return std::sqrt(x);}
};std::vector<double> values = {1.0, 4.0, 9.0, 16.0};
std::vector<double> roots;// 计算平方根
std::transform(values.begin(), values.end(), std::back_inserter(roots), SquareRoot());

七、仿函数适配器

1. 绑定器(Binder):

#include <functional>
#include <algorithm>using namespace std::placeholders; // 用于 _1, _2 等占位符// 绑定第一个参数为10
auto greaterThan10 = std::bind(std::greater<int>(), _1, 10);std::vector<int> data = {5, 12, 8, 15, 3};
int count = std::count_if(data.begin(), data.end(), greaterThan10); // 2

2. 取反器(Negator):

// 创建不等于5的谓词
auto notEqual5 = std::not1(std::bind2nd(std::equal_to<int>(), 5));count = std::count_if(data.begin(), data.end(), notEqual5); // 4

八、仿函数 vs Lambda表达式

比较:

特性仿函数Lambda表达式
语法简洁性❌ 需要完整类定义✅ 简洁
状态管理✅ 显式成员变量✅ 捕获列表
复用性✅ 可多次使用❌ 通常一次性
模板支持✅ 完全支持⚠️ C++20起支持
调试✅ 更容易❌ 匿名类型

转换示例:

// Lambda表达式
auto lambda = [](int a, int b) { return a * b; };// 等效仿函数
class Multiplier {
public:int operator()(int a, int b) const {return a * b;}
};

九、高级技巧与最佳实践

1. 函数组合:

template <typename F, typename G>
class Compose {F f;G g;
public:Compose(F f, G g) : f(f), g(g) {}template <typename T>auto operator()(T x) const -> decltype(f(g(x))) {return f(g(x));}
};// 使用:先平方再取负
Compose<std::negate<double>, std::multiplies<double>> squareAndNegate(std::negate<double>(), std::multiplies<double>());double result = squareAndNegate(4.0); // -16.0

2. 性能优化:

  • 声明operator()inline
  • 对于简单操作,使用空仿函数(无状态)
  • 优先传递仿函数对象而非函数指针

3. 现代C++改进(C++11/14/17/20):

  • 自动类型推导auto关键字简化声明
  • 泛型Lambda(C++14):
    auto genericAdder = [](auto a, auto b) { return a + b; };
    
  • 模板Lambda(C++20):
    auto templated = []<typename T>(T a, T b) { return a + b; };
    

十、实际应用案例

1. 自定义排序:

struct Person {std::string name;int age;double salary;
};// 多级排序仿函数
class PersonSorter {
public:bool operator()(const Person& a, const Person& b) const {// 先按年龄升序if (a.age != b.age) return a.age < b.age;// 年龄相同按薪资降序if (a.salary != b.salary)return a.salary > b.salary;// 最后按名字升序return a.name < b.name;}
};std::vector<Person> people;
std::sort(people.begin(), people.end(), PersonSorter());

2. 策略模式实现:

// 支付策略接口
class PaymentStrategy {
public:virtual double calculate(double amount) const = 0;virtual ~PaymentStrategy() = default;
};// 信用卡支付策略
class CreditCardStrategy : public PaymentStrategy {double feeRate;
public:CreditCardStrategy(double rate) : feeRate(rate) {}double calculate(double amount) const override {return amount * (1 + feeRate);}
};// 支付处理器
class PaymentProcessor {PaymentStrategy* strategy;
public:void setStrategy(PaymentStrategy* s) { strategy = s; }double processPayment(double amount) {return strategy->calculate(amount);}
};

总结:仿函数的优势与选择

何时使用仿函数:

  1. 需要保持状态或多次复用
  2. 作为模板参数传递
  3. 需要复杂的行为或策略
  4. 需要多态行为
  5. 性能敏感场景(内联优化)

何时使用Lambda:

  1. 简单、一次性操作
  2. 需要捕获局部变量
  3. 代码简洁性优先
  4. C++11及以上环境

仿函数设计原则:

  1. 保持operator()const(除非需要修改状态)
  2. 提供清晰的命名
  3. 确保无副作用(或明确文档说明)
  4. 对于模板仿函数,提供类型约束(C++20概念)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Bean生命周期七步曲:定义、实例化、初始化、使用、销毁

各位小猿&#xff0c;程序员小猿开发笔记&#xff0c;希望大家共同进步。 引言 1.整体流程图 2.各阶段分析 1️⃣定义阶段 1.1 定位资源 Spring 扫描 Component、Service、Controller 等注解的类或解析 XML/Java Config 中的 Bean 定义 1.2定义 BeanDefinition 解析类信息…

API安全监测工具:数字经济的免疫哨兵

&#x1f4a5; 企业的三重致命威胁 1. 漏洞潜伏的定时炸弹 某支付平台未检测出API的批量数据泄露漏洞&#xff0c;导致230万用户信息被盗&#xff0c;面临GDPR 1.8亿欧元罚单&#xff08;IBM X-Force 2024报告&#xff09;。传统扫描器对逻辑漏洞漏检率超40%&#xff08;OWASP基…

Matplotlib详细教程(基础介绍,参数调整,绘图教程)

目录 一、初识Matploblib 1.1 安装 Matplotlib 1.2、Matplotlib 的两种接口风格 1.3、Figure 和 Axes 的深度理解 1.4 设置画布大小 1.5 设置网格线 1.6 设置坐标轴 1.7 设置刻度和标签 1.8 添加图例和标题 1.9 设置中文显示 1.10 调整子图布局 二、常用绘图教程 2…

Redis高可用架构演进面试笔记

1. 主从复制架构 核心概念Redis单节点并发能力有限&#xff0c;通过主从集群实现读写分离提升性能&#xff1a; Master节点&#xff1a;负责写操作Slave节点&#xff1a;负责读操作&#xff0c;从主节点同步数据 主从同步流程 全量同步&#xff08;首次同步&#xff09;建立连接…

无人机保养指南

定期清洁无人机在使用后容易积累灰尘、沙砾等杂物&#xff0c;需及时清洁。使用软毛刷或压缩空气清除电机、螺旋桨和机身缝隙中的杂质。避免使用湿布直接擦拭电子元件&#xff0c;防止短路。电池维护锂电池是无人机的核心部件&#xff0c;需避免过度放电或充电。长期存放时应保…

vlm MiniCPM 学习部署实战

目录 开源地址&#xff1a; 模型repo下载&#xff1a; 单图片demo&#xff1a; 多图推理demo&#xff1a; 论文学习笔记&#xff1a; 部署完整教程&#xff1a; 微调教程&#xff1a; 部署&#xff0c;微调教程&#xff0c;视频实测 BitCPM4 技术报告 创意&#xff1…

92套毕业相册PPT模版

致青春某大学同学聚会PPT模版&#xff0c;那些年我们一起走过的岁月PPT模版&#xff0c;某学院某班同学联谊会PPT模版&#xff0c;匆匆那年PPT模版&#xff0c;青春的纪念册PPT模版&#xff0c;栀子花开PPT模版&#xff0c;毕业纪念册PPT模版。 92套毕业相册PPT模版&#xff1…

爬虫基础概念

网络爬虫概述 概念 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也称为网络蜘蛛&#xff08;Web Spider&#xff09;或机器人&#xff08;Bot&#xff09;&#xff0c;是一种自动化程序&#xff0c;用于系统地浏览互联网并收集网页信息。它模拟人类浏览器行为&…

java8 stream流操作的flatMap

我们来详细解释一下 Java 8 Stream API 中的 flatMap 操作。理解 flatMap 的关键在于将其与 map 操作进行对比。​​核心概念&#xff1a;​​​​map 操作&#xff1a;​​作用&#xff1a;将一个流中的每个元素​​转换​​为另一个元素&#xff08;类型可以不同&#xff09;…

开源UI生态掘金:从Ant Design二次开发到行业专属组件的技术变现

开源UI生态掘金&#xff1a;从Ant Design二次开发到行业专属组件的技术变现内容摘要在开源UI生态中&#xff0c;Ant Design作为一款广受欢迎的UI框架&#xff0c;为开发者提供了强大的基础组件。然而&#xff0c;面对不同行业的特定需求&#xff0c;仅仅依靠现有的组件往往难以…

Object Sense (OSE):一款从编辑器脚本发展起来的编程语言

引言&#xff1a;从Vim编辑器走出的语言在编程语言的世界里&#xff0c;许多革命性的创新往往源于看似简单的工具。Object Sense&#xff08;简称OSE&#xff09;的诞生&#xff0c;便与一款经典文本编辑器——Vim息息相关。它的前身是Vim的脚本语言VimL&#xff08;Vimscript&…

我考PostgreSQL中级专家证书二三事

1. 为什么选择PGCE&#xff1f;PostgreSQL的开源特性、高性能和高扩展性早已让我心生向往&#xff0c;而PGCE认证不仅是对技术能力的认可&#xff0c;更是一张通往更高职业舞台的“通行证”。官方资料提到&#xff0c;PGCE考试涵盖性能优化、高可用架构、复杂查询处理、内核原理…

Java 动态导出 Word 登记表:多人员、分页、动态表格的最佳实践

本文详细讲解如何使用 Java 动态导出包含多人员报名表的 Word 文档&#xff0c;每人占据独立一页&#xff0c;并支持动态表格行&#xff08;如个人经历&#xff09;。我们对比了多种实现方案&#xff0c;最终推荐基于 Freemarker XML 模板 或 docx4j 的灵活方式&#xff0c;并…

【element-ui el-table】多选表格勾选时默认勾选了全部,row-key绑定异常问题解决

项目场景&#xff1a; Element-UI的el-table组件row-key使用问题 同一个页面使用了几个table&#xff0c;这几个table都使用了多选&#xff0c;row-key属性&#xff0c;其中row-key的绑定方式都是用的静态绑定&#xff0c;row-key“username”或row-key“id”&#xff0c;可正常…

C#注释技巧与基础编程示例

以下是一个包含基础注释的 C# 程序示例&#xff0c;展示了 C# 中各类注释的使用方法&#xff1a;using System;namespace BasicCSharpProgram {/// <summary>/// Program 类是应用程序的入口点/// 包含 Main 方法作为程序执行的起点/// </summary>public class Pro…

极客大挑战2019-HTTP

涵盖知识&#xff1a;UA头伪造漏洞&#xff1a;全称&#xff1a;User-Agent 这个部分包含我们所使用的操作系统版本&#xff0c;cpu&#xff0c;浏览器类型等。来源伪造漏洞&#xff1a;在http请求头中会携带一个Referer&#xff0c;这个用来表示服务器用户是从哪个地方来的X-F…

谈谈ArrayList与Vector的理解?

目录 扩容机制 ArrayList扩容源码 Vector扩容源码 二者区别 扩展&#xff1a;stack(栈&#xff09; 1.创建stack对象 2. 入栈(先进后出&#xff09; 3.出栈 扩展&#xff1a;举个例子&#xff1a;实现下字符串逆置&#xff0c;利用stack栈来实现。 从接口实现上&#xff…

【Linux庖丁解牛】— 多线程同步 !

1. 什么是线程同步为什么会有线程同步&#xff0c;那一定是有了新问题。互斥可以解决临界资源被同时访问的问题&#xff0c;但是纯互斥也会带来新的问题。由于当前被执行的线程离cpu最近【其他线程被阻塞挂起还要被唤醒】&#xff0c;所以&#xff0c;当前进程对于竞争锁天然就…

基于arduino uno r3主控的环境监测系统设计-1

准备设计arduino uno r3为主控的环境监测系统&#xff0c;通过传感器采集TVOC&#xff08;总挥发性有机物&#xff09;、HCHO&#xff08;甲醛&#xff09;和eCO2&#xff08;等效二氧化碳&#xff09;数据&#xff0c;并显示在LCD屏幕上&#xff0c;同时支持数据记录到SD卡&am…

ITIL 4:云计算与微服务对组织架构的影响

这几年&#xff0c;很多组织在推进数字化转型时遇到一个共同的问题&#xff1a;业务节奏越来越快&#xff0c;但内部协作的“架构”却越来越跟不上节奏。技术架构的变革&#xff0c;必须同步推动组织架构的重塑。特别是随着云计算和微服务架构的广泛应用&#xff0c;这种影响愈…