1. C++仿函数与遍历算法深度解析
在C++标准模板库(STL)的日常使用中,仿函数(functor)和遍历算法是提升代码质量和开发效率的两大核心武器。作为从C++98时代就开始使用STL的老兵,我见过太多开发者对这些特性仅停留在表面理解。今天我们就来彻底拆解for_each和transform这两个最常用的遍历算法,以及它们与仿函数的完美配合。
提示:本文所有代码示例基于C++17标准,在GCC 9.0以上版本测试通过。建议读者在支持C++17的编译环境中实践。
1.1 仿函数:STL算法的灵魂伴侣
仿函数不是简单的函数指针替代品,而是一个可以携带状态的函数对象。先看一个典型场景:我们需要统计容器中大于某阈值的元素数量。用普通函数实现是这样的:
cpp复制bool is_greater_than(int value, int threshold) {
return value > threshold;
}
// 使用时需要绑定阈值参数
std::count_if(vec.begin(), vec.end(),
std::bind(is_greater_than, std::placeholders::_1, 42));
而用仿函数实现则更加优雅:
cpp复制class GreaterThan {
public:
explicit GreaterThan(int t) : threshold(t) {}
bool operator()(int value) const {
return value > threshold;
}
private:
int threshold;
};
// 使用简洁明了
std::count_if(vec.begin(), vec.end(), GreaterThan(42));
仿函数的三大优势:
- 状态保持:构造函数参数初始化内部状态
- 内联优化:编译器更容易优化operator()调用
- 类型安全:编译期就能检查类型匹配
1.2 for_each算法实战详解
std::for_each可能是STL中最被低估的算法。它不只是简单的循环替代品,而是提供了更安全的元素访问方式。看这个典型错误示例:
cpp复制std::vector<int> vec = {1, 2, 3};
for(auto it = vec.begin(); it != vec.end(); ++it) {
if(*it % 2 == 0) {
vec.erase(it); // 危险!迭代器失效
}
}
用for_each结合erase-remove惯用法可以安全处理:
cpp复制vec.erase(std::remove_if(vec.begin(), vec.end(),
[](int x){ return x % 2 == 0; }), vec.end());
但更推荐使用C++20的ranges版本:
cpp复制std::erase_if(vec, [](int x){ return x % 2 == 0; });
for_each的进阶用法是配合mutable仿函数实现状态累积:
cpp复制class AverageCalculator {
public:
void operator()(int value) {
sum += value;
++count;
}
double get() const { return sum / count; }
private:
double sum = 0;
int count = 0;
};
auto avg = std::for_each(vec.begin(), vec.end(), AverageCalculator());
std::cout << "Average: " << avg.get();
1.3 transform算法的魔法
std::transform是STL中最强大的算法之一,它能将一种容器转换为另一种容器。经典用法:
cpp复制std::vector<int> src = {1, 2, 3};
std::vector<std::string> dst;
dst.reserve(src.size());
std::transform(src.begin(), src.end(), std::back_inserter(dst),
[](int x) { return std::to_string(x * 2); });
但transform的
