1. C++标准库算法概述
作为C++开发者,我们每天都在与各种数据结构和算法打交道。STL(Standard Template Library)提供了一套强大而高效的算法库,涵盖了从基础查找排序到复杂数值计算的方方面面。掌握这些算法不仅能提升代码效率,更能让我们写出更优雅、更易维护的代码。
STL算法主要分为几大类:非修改序列算法、修改序列算法、排序相关算法、堆算法、数值算法等。每种算法都有其特定的应用场景和性能特征。在实际开发中,我们需要根据具体需求选择合适的算法,并理解其底层实现原理,才能充分发挥其威力。
2. 非修改序列算法详解
2.1 查找算法:find与find_if
find和find_if是最基础的查找算法,用于在序列中定位特定元素。它们的区别在于查找条件的不同:
cpp复制vector<int> nums = {1, 3, 5, 7, 9};
// 查找值为5的元素
auto it = find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
cout << "found: " << *it << endl; // 输出:5
}
// 查找第一个大于6的元素
auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
return x > 6;
});
cout << "first >6: " << *it2 << endl; // 输出:7
注意:
find使用值相等比较,而find_if接受一个谓词函数,可以定义更复杂的查找条件。这两个算法的时间复杂度都是O(n),因为它们需要线性遍历序列。
2.2 计数算法:count与count_if
计数算法用于统计序列中满足特定条件的元素数量:
cpp复制std::vector<int> vec = {1, 2, 3, 2, 4, 2};
int cnt = std::count(vec.begin(), vec.end(), 2); // 计数2的个数,结果为3
int even_cnt = std::count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // 偶数个数,结果为4
在实际应用中,count_if特别有用,因为它允许我们通过lambda表达式定义任意的计数条件。例如,统计字符串向量中长度大于5的字符串数量:
cpp复制vector<string> words = {"apple", "banana", "cherry", "date"};
int long_words = count_if(words.begin(), words.end(),
[](const string& s) { return s.length() > 5; });
2.3 遍历算法:for_each
for_each算法提供了一种函数式的方式来处理序列中的每个元素:
cpp复制std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // 将每个元素乘以2
});
// 现在vec变为{2, 4, 6, 8, 10}
与传统的for循环相比,for_each有几个优势:
- 更清晰的语义表达"对每个元素做某事"
- 可以避免手动管理迭代器
- 更容易并行化(C++17引入了并行执行策略)
2.4 比较算法:equal与mismatch
比较两个序列是否相等或找出第一个不匹配的位置:
cpp复制vector<int> a = {1, 2, 3};
vector<int> b = {1, 2, 4};
vector<int> c = {1, 2, 3, 4};
// 比较a和b的前3个元素
bool is_equal = equal(a.begin(), a.end(), b.begin());
cout << "a == b? " << boolalpha << is_equal << endl; // 输出:false
// 查找a和c的第一个不匹配元素
auto mis = mismatch(a.begin(), a.end(), c.begin());
if (mis.first != a.end()) {
cout << "mismatch: " << *mis.first << " vs " << *mis.second << endl; // 无输出(a和c前3元素相等)
}
equal和mismatch在单元测试、数据校验等场景非常有用。需要注意的是,它们默认比较的是元素的值相等性,对于自定义类型需要提供比较函数。
2.5 条件检查算法:all_of/any_of/none_of
这组算法用于检查序列中的元素是否满足特定条件:
cpp复制std::vector<int> vec = {2, 4, 6, 8};
bool all_even = std::all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true
bool any_odd = std::any_of(vec.begin(), vec.end(), [](int x) {
return x % 2 != 0;
}); // false
bool none_negative = std::none_of(vec.begin(), vec.end(), [](int x) {
return x < 0;
}); // true
这些算法可以替代很多手写的条件检查循环,使代码更简洁。例如,检查用户输入是否全部为数字:
cpp复制string input = "12345";
bool is_digits = all_of(input.begin(), input.end(), ::isdigit);
3. 修改序列算法详解
3.1 复制算法:copy与copy_if
复制算法用于将一个序列的内容复制到另一个位置:
cpp复制vector<int> src = {1, 2, 3, 4, 5};
vector<int> dest(5); // 需预先分配足够空间
// 复制所有元素
copy(src.begin(), src.end(), dest.begin()); // dest: [1,2,3,4,5]
// 复制偶数元素到新容器
vector<int> evens;
copy_if(src.begin(), src.end(), back_inserter(evens), [](int x) {
return x % 2 == 0;
}); // evens: [2,4]
提示:
back_inserter是一个非常有用的迭代器适配器,它会自动调用容器的push_back方法,无需预先分配空间。这在不确定输出大小时特别有用。
3.2 变换算法:transform
transform算法对序列中的每个元素应用一个函数,并将结果存储到目标位置:
cpp复制vector<int> nums = {1, 2, 3};
vector<int> squares(3);
// 单参数版本:计算平方
transform(nums.begin(), nums.end(), squares.begin(), [](int x) {
return x * x;
}); // squares: [1,4,9]
// 双参数版本:两序列元素相加
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> sum(3);
transform(a.begin(), a.end(), b.begin(), sum.begin(), [](int x, int y) {
return x + y;
}); // sum: [5,7,9]
transform是函数式编程风格的典型代表,它避免了显式循环,使代码更清晰。在实际应用中,我们可以用它来实现各种元素级转换,如字符串大小写转换、坐标变换等。
3.3 替换算法:replace系列
替换算法用于修改序列中的特定元素:
cpp复制vector<int> nums = {1, 2, 3, 2, 5};
// 替换所有2为20
replace(nums.begin(), nums.end(), 2, 20); // nums: [1,20,3,20,5]
// 替换大于10的元素为0
replace_if(nums.begin(), nums.end(), [](int x) {
return x > 10;
}, 0); // nums: [1,0,3,0,5]
// 复制时替换3为300(原容器不变)
vector<int> res;
replace_copy(nums.begin(), nums.end(), back_inserter(res), 3, 300); // res: [1,0,300,0,5]
replace_copy特别适合需要保留原始数据的情况。例如,在日志处理中,我们可能需要将敏感信息替换为星号,同时保留原始日志:
cpp复制vector<string> logs = {"user=admin pwd=1234", "user=guest pwd=5678"};
vector<string> sanitized;
replace_copy_if(logs.begin(), logs.end(), back_inserter(sanitized),
[](const string& s) { return s.find("pwd=") != string::npos; },
"[PASSWORD REDACTED]");
3.4 删除算法:remove系列
删除算法用于从序列中移除特定元素:
cpp复制vector<int> nums = {1, 2, 3, 2, 4};
// 逻辑删除所有2(移动到末尾)
auto new_end = remove(nums.begin(), nums.end(), 2); // nums: [1,3,4,2,2]
// 物理删除(真正移除元素)
nums.erase(new_end, nums.end()); // nums: [1,3,4]
// 结合lambda删除偶数
nums = {1, 2, 3, 4, 5};
nums.erase(remove_if(nums.begin(), nums.end(), [](int x) {
return x % 2 == 0;
}), nums.end()); // nums: [1,3,5]
重要说明:
remove和remove_if实际上并不删除元素,而是将要保留的元素移动到序列前面,返回新的逻辑结尾迭代器。真正的删除需要通过容器的erase方法完成。这种设计是为了提高效率,避免频繁的内存分配。
3.5 去重算法:unique
unique算法用于移除连续的重复元素:
cpp复制std::vector<int> vec = {1, 1, 2, 2, 3, 3, 3, 4, 5};
auto last = std::unique(vec.begin(), vec.end());
vec.erase(last, vec.end()); // vec变为{1, 2, 3, 4, 5}
需要注意的是,unique只移除连续的重复元素。如果要移除所有重复元素,需要先对序列排序:
cpp复制vector<int> nums = {1, 2, 1, 3, 2, 4};
sort(nums.begin(), nums.end());
auto last = unique(nums.begin(), nums.end());
nums.erase(last, nums.end()); // nums: [1,2,3,4]
3.6 反转与旋转算法
反转和旋转算法可以改变序列中元素的顺序:
cpp复制// 反转序列
std::vector<int> vec = {1, 2, 3, 4, 5};
std::reverse(vec.begin(), vec.end()); // vec变为{5, 4, 3, 2, 1}
// 旋转序列
std::vector<int> vec2 = {1, 2, 3, 4, 5};
std::rotate(vec2.begin(), vec2.begin() + 2, vec2.end()); // 以3为起点旋转,vec变为{3, 4, 5, 1, 2}
旋转算法在循环移位、环形缓冲区等场景非常有用。例如,实现一个循环队列的出队操作:
cpp复制template<typename T>
void CircularQueue<T>::dequeue() {
if (!empty()) {
std::rotate(data.begin(), data.begin() + 1, data.end());
data.pop_back();
}
}
3.7 随机重排算法:shuffle
shuffle算法用于随机打乱序列中的元素顺序:
cpp复制#include <random>
#include <algorithm>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(vec.begin(), vec.end(), g); // 随机打乱vec中的元素
与传统的rand()函数相比,C++11的随机数库提供了更高质量的随机数生成器。shuffle常用于游戏开发(如洗牌)、随机抽样等场景。
4. 排序和相关算法
4.1 排序算法:sort与stable_sort
STL提供了多种排序算法,各有特点:
cpp复制std::vector<int> vec = {5, 3, 1, 4, 2};
std::sort(vec.begin(), vec.end()); // 默认升序,vec变为{1, 2, 3, 4, 5}
std::sort(vec.begin(), vec.end(), std::greater<int>()); // 降序,vec变为{5, 4, 3, 2, 1}
// 自定义排序:按字符串长度排序
std::vector<std::string> words = {"apple", "banana", "cherry", "date"};
std::sort(words.begin(), words.end(), [](const auto& a, const auto& b) {
return a.length() < b.length();
});
// 稳定排序:保持相等元素的相对顺序
std::vector<std::pair<int, int>> pairs = {{1, 2}, {2, 1}, {1, 1}, {2, 2}};
std::stable_sort(pairs.begin(), pairs.end(), [](const auto& a, const auto& b) {
return a.first < b.first;
});
性能提示:
sort通常使用introsort算法,平均时间复杂度为O(n log n),但不保证稳定性。stable_sort使用归并排序,保证稳定性但可能有更高的空间开销。对于小型数据集(如少于32个元素),sort可能会退化为插入排序以获得更好的局部性。
4.2 部分排序:partial_sort与nth_element
部分排序算法用于只需要部分有序结果的场景:
cpp复制// 部分排序:找出并排序最小的3个元素
std::vector<int> vec = {5, 3, 1, 4, 2, 6};
std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// 现在vec前三个元素是1, 2, 3,后面是未排序的4, 5, 6
// 找出第3小的元素(索引2)
std::vector<int> vec2 = {5, 3, 1, 4, 2, 6};
std::nth_element(vec2.begin(), vec2.begin() + 2, vec2.end());
// vec2[2]是3,它左边的元素<=3,右边的>=3
partial_sort适合需要前N个有序元素的场景,如排行榜。nth_element则适合快速选择问题,如找中位数或百分位数。
4.3 二分查找算法
二分查找算法要求输入序列必须是有序的:
cpp复制vector<int> sorted = {1, 3, 3, 5, 7}; // 必须先排序
// 判断3是否存在
bool exists = binary_search(sorted.begin(), sorted.end(), 3); // true
// 查找第一个>=3的元素
auto lb = lower_bound(sorted.begin(), sorted.end(), 3);
cout << "lower_bound index: " << lb - sorted.begin() << endl; // 输出:1
// 查找第一个>3的元素
auto ub = upper_bound(sorted.begin(), sorted.end(), 3);
cout << "upper_bound index: " << ub - sorted.begin() << endl; // 输出:3
二分查找算法的时间复杂度是O(log n),远优于线性查找。lower_bound和upper_bound特别适合处理包含重复元素的序列,可以快速定位到相等元素的范围。
4.4 合并算法:merge
merge算法合并两个已排序的序列,产生一个新的已排序序列:
cpp复制vector<int> a = {1, 3, 5};
vector<int> b = {2, 4, 6};
vector<int> merged(a.size() + b.size());
// 合并a和b(均需已排序)
merge(a.begin(), a.end(), b.begin(), b.end(), merged.begin()); // merged: [1,2,3,4,5,6]
merge是归并排序的核心操作,也常用于合并多个有序数据源。例如,合并多个用户的已排序联系人列表:
cpp复制vector<Contact> contacts1 = getContacts(user1);
vector<Contact> contacts2 = getContacts(user2);
vector<Contact> allContacts(contacts1.size() + contacts2.size());
merge(contacts1.begin(), contacts1.end(),
contacts2.begin(), contacts2.end(),
allContacts.begin(), [](const Contact& a, const Contact& b) {
return a.name < b.name;
});
5. 堆算法
STL提供了一组堆操作算法,可以将任何序列当作二叉堆来处理:
cpp复制std::vector<int> vec = {4, 1, 3, 2, 5};
// 构建最大堆
std::make_heap(vec.begin(), vec.end()); // vec变为{5, 4, 3, 2, 1}
// 添加新元素并维护堆性质
vec.push_back(6);
std::push_heap(vec.begin(), vec.end()); // vec变为{6, 4, 5, 2, 1, 3}
// 弹出堆顶元素
std::pop_heap(vec.begin(), vec.end()); // 将最大元素移到末尾,vec变为{5, 4, 3, 2, 1, 6}
int max_val = vec.back(); // 获取最大元素6
vec.pop_back(); // 移除最大元素
// 堆排序
std::sort_heap(vec.begin(), vec.end()); // 将堆排序为升序序列,vec变为{1, 2, 3, 4, 5}
堆算法常用于实现优先队列。例如,实现一个任务调度系统,总是执行优先级最高的任务:
cpp复制vector<Task> tasks = getTasks();
make_heap(tasks.begin(), tasks.end(), ComparePriority());
while (!tasks.empty()) {
pop_heap(tasks.begin(), tasks.end(), ComparePriority());
Task current = tasks.back();
tasks.pop_back();
execute(current);
// 新任务到达
if (hasNewTask()) {
tasks.push_back(getNewTask());
push_heap(tasks.begin(), tasks.end(), ComparePriority());
}
}
6. 数值算法
<numeric>头文件提供了一组数值计算算法:
6.1 累加算法:accumulate
accumulate不仅可以计算累加和,还可以实现各种归约操作:
cpp复制#include <numeric>
// 基本累加
std::vector<int> vec = {1, 2, 3, 4, 5};
int sum = std::accumulate(vec.begin(), vec.end(), 0); // 15
// 累乘
int product = std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>()); // 120
// 字符串连接
std::vector<std::string> words = {"Hello", " ", "World"};
std::string sentence = std::accumulate(words.begin(), words.end(), std::string("")); // "Hello World"
// 自定义操作:计算点积
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
double dot_product = std::accumulate(a.begin(), a.end(), 0.0,
[&b, i = 0](double acc, int x) mutable {
return acc + x * b[i++];
}); // 32.0
6.2 内积算法:inner_product
inner_product专门用于计算两个序列的内积(点积):
cpp复制std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
int dot = std::inner_product(a.begin(), a.end(), b.begin(), 0); // 1*4 + 2*5 + 3*6 = 32
这个算法也可以用于更一般的线性代数运算,如矩阵乘法。
6.3 序列生成算法:iota
iota用连续递增的值填充序列:
cpp复制std::vector<int> vec(5);
std::iota(vec.begin(), vec.end(), 10); // 填充为10, 11, 12, 13, 14
这在需要生成索引或标识符时非常有用。例如,为一系列对象分配唯一ID:
cpp复制vector<Employee> employees(100);
vector<int> ids(employees.size());
iota(ids.begin(), ids.end(), 1000); // 分配ID从1000开始
for (size_t i = 0; i < employees.size(); ++i) {
employees[i].id = ids[i];
}
6.4 部分和与相邻差算法
cpp复制// 计算部分和
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(src.size());
std::partial_sum(src.begin(), src.end(), dst.begin()); // dst变为{1, 3, 6, 10, 15}
// 计算相邻差值
std::vector<int> src2 = {1, 2, 3, 4, 5};
std::vector<int> dst2(src2.size());
std::adjacent_difference(src2.begin(), src2.end(), dst2.begin()); // dst变为{1, 1, 1, 1, 1}
这些算法在信号处理、时间序列分析等领域非常有用。例如,计算股票价格的日收益率:
cpp复制vector<double> prices = {100.0, 101.5, 102.0, 100.5, 99.0};
vector<double> returns(prices.size());
adjacent_difference(prices.begin(), prices.end(), returns.begin(),
[](double a, double b) { return (a - b) / b; });
returns.erase(returns.begin()); // 移除第一个无效值
7. 其他实用算法
7.1 生成算法:generate与generate_n
这些算法用生成函数填充序列:
cpp复制// 用生成函数填充整个序列
std::vector<int> vec(5);
int n = 0;
std::generate(vec.begin(), vec.end(), [&n]() {
return n++;
}); // 填充为0, 1, 2, 3, 4
// 用生成函数填充前n个元素
std::vector<int> vec2(5);
int m = 10;
std::generate_n(vec2.begin(), 3, [&m]() {
return m++;
}); // 前三个元素为10, 11, 12,后两个保持不变
这在需要生成测试数据或模拟数据时特别有用。例如,生成随机测试用例:
cpp复制vector<double> testData(1000);
random_device rd;
mt19937 gen(rd());
uniform_real_distribution<> dis(0.0, 1.0);
generate(testData.begin(), testData.end(), [&]() { return dis(gen); });
7.2 集合算法
STL提供了一组集合操作算法,要求输入序列必须是有序的:
cpp复制std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {3, 4, 5, 6, 7};
std::vector<int> result;
// 并集
std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2, 3, 4, 5, 6, 7}
// 交集
result.clear();
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{3, 4, 5}
// 差集 (v1 - v2)
result.clear();
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2}
// 对称差集 (v1 ∪ v2 - v1 ∩ v2)
result.clear();
std::set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(result));
// result为{1, 2, 6, 7}
这些算法在数据库操作、集合运算等场景非常有用。例如,实现一个社交网络的好友推荐系统:
cpp复制vector<int> userFriends = getUserFriends(userId);
vector<int> friendFriends = getFriendsOfFriends(userId);
sort(userFriends.begin(), userFriends.end());
sort(friendFriends.begin(), friendFriends.end());
// 找出好友的好友中还不是自己好友的人
vector<int> recommendations;
set_difference(friendFriends.begin(), friendFriends.end(),
userFriends.begin(), userFriends.end(),
back_inserter(recommendations));
8. 算法选择与性能优化
8.1 算法复杂度对比
了解不同算法的时间复杂度对于编写高效代码至关重要:
| 算法类别 | 典型算法 | 平均时间复杂度 | 备注 |
|---|---|---|---|
| 非修改序列 | find, count | O(n) | 线性遍历 |
| 排序算法 | sort | O(n log n) | 快速排序变体 |
| 稳定排序 | stable_sort | O(n log n) | 归并排序 |
| 二分查找 | lower_bound | O(log n) | 要求有序输入 |
| 堆操作 | push_heap | O(log n) | 单个操作 |
| 集合操作 | set_union | O(n+m) | 要求有序输入 |
8.2 算法选择指南
根据常见需求场景选择合适的算法:
-
查找元素:
- 无序序列:
find、find_if - 有序序列:
lower_bound、binary_search
- 无序序列:
-
排序需求:
- 基本排序:
sort - 稳定排序:
stable_sort - 部分排序:
partial_sort、nth_element
- 基本排序:
-
数据转换:
- 元素级转换:
transform - 过滤复制:
copy_if - 替换元素:
replace系列
- 元素级转换:
-
数值计算:
- 累加/归约:
accumulate - 内积计算:
inner_product - 差分/积分:
adjacent_difference、partial_sum
- 累加/归约:
8.3 性能优化技巧
-
避免不必要的拷贝:使用移动语义或视图(如C++20的ranges)
cpp复制// 不好的做法:创建临时拷贝 vector<int> filtered; copy_if(data.begin(), data.end(), back_inserter(filtered), pred); process(filtered); // 更好的做法:使用视图(C++20) auto filtered_view = data | views::filter(pred); process(filtered_view); -
利用算法并行化(C++17):
cpp复制vector<int> bigData(1'000'000); // 并行排序 sort(execution::par, bigData.begin(), bigData.end()); -
预分配内存:对于知道输出大小的算法(如
transform),预先分配足够空间避免多次分配cpp复制vector<int> src(1000); vector<int> dest(src.size()); // 预分配 transform(src.begin(), src.end(), dest.begin(), fn); -
选择合适的数据结构:算法性能很大程度上取决于数据结构的选择。例如:
- 频繁查找:使用
set或unordered_set - 频繁插入删除:使用
list而非vector - 需要同时快速查找和有序遍历:使用
map
- 频繁查找:使用
9. 实际应用案例
9.1 案例一:数据分析管道
假设我们需要处理一个大型数据集,计算每个用户的平均交易金额:
cpp复制struct Transaction {
int userId;
double amount;
// 其他字段...
};
vector<Transaction> transactions = loadTransactions();
// 按用户ID排序
sort(transactions.begin(), transactions.end(),
[](const auto& a, const auto& b) { return a.userId < b.userId; });
// 计算每个用户的交易总额和次数
vector<pair<int, double>> userTotals;
vector<int> userCounts;
auto it = transactions.begin();
while (it != transactions.end()) {
// 查找相同用户ID的范围
auto range_end = find_if_not(it, transactions.end(),
[id = it->userId](const auto& t) { return t.userId == id; });
// 计算该用户的总金额
double total = accumulate(it, range_end, 0.0,
[](double sum, const Transaction& t) { return sum + t.amount; });
userTotals.emplace_back(it->userId, total);
userCounts.push_back(distance(it, range_end));
it = range_end;
}
// 计算平均值
vector<pair<int, double>> userAverages(userTotals.size());
transform(userTotals.begin(), userTotals.end(), userCounts.begin(),
userAverages.begin(), [](const auto& total, int count) {
return make_pair(total.first, total.second / count);
});
9.2 案例二:文本处理
处理文本数据,统计词频并找出最常见的单词:
cpp复制string text = loadText();
vector<string> words = splitIntoWords(text);
// 转换为小写
transform(words.begin(), words.end(), words.begin(),
[](string s) {
transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
});
// 排序以便统计
sort(words.begin(), words.end());
// 统计词频
vector<pair<string, int>> wordCounts;
auto it = words.begin();
while (it != words.end()) {
auto range_end = find_if_not(it, words.end(),
[word = *it](const string& s) { return s == word; });
int count = distance(it, range_end);
if (count > 1) { // 只统计出现多次的单词
wordCounts.emplace_back(*it, count);
}
it = range_end;
}
// 按频率降序排序
sort(wordCounts.begin(), wordCounts.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
// 输出前10个最常见的单词
int n = min(10, static_cast<int>(wordCounts.size()));
for_each(wordCounts.begin(), wordCounts.begin() + n,
[](const auto& entry) {
cout << entry.first << ": " << entry.second << endl;
});
9.3 案例三:游戏开发
在游戏开发中,STL算法可以用于各种场景,如实体更新、碰撞检测等:
cpp复制vector<GameEntity> entities = getGameEntities();
// 更新所有实体
for_each(entities.begin(), entities.end(),
[deltaTime](GameEntity& e) { e.update(deltaTime); });
// 移除已销毁的实体
entities.erase(
remove_if(entities.begin(), entities.end(),
[](const GameEntity& e) { return e.isDestroyed(); }),
entities.end());
// 按深度排序渲染(稳定排序保持相同深度元素的顺序)
stable_sort(entities.begin(), entities.end(),
[](const GameEntity& a, const GameEntity& b) {
return a.getDepth() < b.getDepth();
});
// 渲染所有实体
for_each(entities.begin(), entities.end(),
[](const GameEntity& e) { e.render(); });
// 处理碰撞
vector<CollisionPair> collisions;
for (auto it1 = entities.begin(); it1 != entities.end(); ++it1) {
auto it2 = find_if(it1 + 1, entities.end(),
[&it1](const GameEntity& e) {
return checkCollision(*it1, e);
});
if (it2 != entities.end()) {
collisions.emplace_back(*it1, *it2);
}
}
10. C++20中的新特性
C++20引入了ranges库和新的算法特性,使代码更加简洁和安全:
10.1 范围算法
范围算法提供了更简洁的语法和更好的安全性:
cpp复制#include <ranges>
#include <algorithm>
vector<int> nums = {1, 2, 3, 4, 5};
// 传统方式
sort(nums.begin(), nums.end());
// C++20范围方式
ranges::sort(nums);
// 管道操作符组合算法
auto even_squares = nums
| views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * x; });
for (int x : even_squares) {
cout << x << " "; // 输出偶数平方:4 16
}
10.2 新的算法
C++20新增了一些有用的算法:
cpp复制// 判断序列是否已排序
bool is_sorted = ranges::is_sorted(nums);
// 查找第一个不满足条件的元素
auto it = ranges::find_if_not(nums, [](int x) { return x < 4; });
// 同时处理两个序列
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
ranges::transform(a, b.begin(), a.begin(), plus<>()); // a变为[5,7,9]
10.3 视图和惰性求值
视图提供了对序列的惰性操作,不会立即创建新的容器:
cpp复制vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 传统方式:立即求值,创建新容器
vector<int> even_squares;
copy_if(nums.begin(), nums.end(), back_inserter(even_squares),
[](int x) { return x % 2 == 0; });
transform(even_squares.begin(), even_squares.end(), even_squares.begin(),
[](int x) { return x * x; });
// C++20视图:惰性求值,不创建中间容器
auto view = nums
| views::filter([](int x) { return x % 2 == 0; })
| views::transform([](int x) { return x * x; });
// 只有在迭代时才会实际计算
for (int x : view) {
cout << x << " "; // 4 16 36 64 100
}
11. 常见问题与解决方案
11.1 如何选择合适的排序算法?
-
sort:默认选择,大多数情况下性能最好- 优点:平均O(n log n)时间复杂度
- 缺点:不保证稳定性
- 适用场景:不需要保持相等元素顺序的一般排序
-
stable_sort:需要保持相等元素顺序时使用- 优点:稳定排序
- 缺点:可能比
sort慢,内存开销更大 - 适用场景:排序包含多个字段的对象,且需要保持某些字段的相对顺序
-
partial_sort:只需要前N个有序元素时使用- 优点:比完全排序更快
- 缺点:不适用于需要完全排序的场景
- 适用场景:排行榜、Top N查询
11.2 为什么remove不真正删除元素?
remove算法的设计基于效率考虑:
- 它只通过移动元素来"逻辑删除",避免了频繁