C++ 生成与变换
引言
在C++ STL(标准模板库)中,生成与变换算法是一组非常实用的工具,它们允许我们以声明式的方式对容器中的元素进行批量操作。本文将详细介绍这些算法,包括如何使用它们来创建新值或转换现有值,这对于数据处理和函数式编程风格的C++代码尤为重要。
生成算法
generate 和 generate_n
generate
和generate_n
是STL中用于填充容器的两个重要算法。它们通过调用指定的生成器函数来创建新值。
generate
generate
算法在指定范围内的每个元素上应用一个生成器函数,并用生成的值替换原有元素。
语法:
void generate(ForwardIterator first, ForwardIterator last, Generator gen);
参数:
first
,last
: 定义操作范围的迭代器gen
: 不接受参数并返回要生成的值的函数对象
示例:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
// 创建一个包含5个元素的向量
std::vector<int> v(5);
// 使用generate填充向量,每个元素都是7
std::generate(v.begin(), v.end(), []() { return 7; });
// 打印向量
for (int n : v) {
std::cout << n << " ";
}
std::cout << "\n"; // 输出: 7 7 7 7 7
// 使用递增的计数器填充向量
int count = 0;
std::generate(v.begin(), v.end(), [&count]() { return count++; });
// 打印向量
for (int n : v) {
std::cout << n << " ";
}
std::cout << "\n"; // 输出: 0 1 2 3 4
return 0;
}
generate_n
generate_n
算法与generate
类似,但它指定要生成的元素数量而不是结束迭代器。
语法:
OutputIterator generate_n(OutputIterator first, Size n, Generator gen);
参数:
first
: 开始填充的迭代器位置n
: 要生成的元素数量gen
: 生成器函数对象
示例:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v(3);
// 使用generate_n填充前2个元素,每个元素都是42
std::generate_n(v.begin(), 2, []() { return 42; });
// 打印向量
for (int n : v) {
std::cout << n << " ";
}
std::cout << "\n"; // 输出: 42 42 0
return 0;
}
generate_n
在处理固定数量元素时特别有用,而generate
适合处理容器的整个范围或特定子范围。
变换算法
transform
transform
算法是STL中最强大的算法之一,它将指定操作应用于范围内的元素,并将结果存储到目标范围。
单输入范围的transform
语法:
OutputIterator transform(InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op);
参数:
first1
,last1
: 输入范围的迭代器result
: 输出范围的起始迭代器op
: 一元操作函数对象
示例:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> result(v.size());
// 对每个元素应用平方操作
std::transform(v.begin(), v.end(), result.begin(),
[](int x) { return x * x; });
// 打印结果
for (int n : result) {
std::cout << n << " ";
}
std::cout << "\n"; // 输出: 1 4 9 16 25
return 0;
}
双输入范围的transform
transform
的另一个版本可以处理两个输入范围,对来自两个范围的元素对应用二元操作。
语法:
OutputIterator transform(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, OutputIterator result,
BinaryOperation binary_op);
参数:
first1
,last1
: 第一个输入范围的迭代器first2
: 第二个输入范围的起始迭代器result
: 输出范围的起始迭代器binary_op
: 二元操作函数对象
示例:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {10, 20, 30, 40, 50};
std::vector<int> result(v1.size());
// 将v1和v2中对应元素相加
std::transform(v1.begin(), v1.end(), v2.begin(), result.begin(),
[](int a, int b) { return a + b; });
// 打印结果
for (int n : result) {
std::cout << n << " ";
}
std::cout << "\n"; // 输出: 11 22 33 44 55
return 0;
}
使用双输入范围版本的transform
时,必须确保第二个输入范围至少与第一个输入范围一样长,否则可能会导致未定义行为。
实际应用案例
案例1: 生成随机数列
使用generate
生成一个包含随机数的向量:
#include <algorithm>
#include <iostream>
#include <vector>
#include <random>
int main() {
// 创建一个随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 100);
// 创建一个包含10个元素的向量
std::vector<int> random_numbers(10);
// 使用generate填充随机数
std::generate(random_numbers.begin(), random_numbers.end(),
[&]() { return dis(gen); });
// 打印结果
std::cout << "随机数列: ";
for (int num : random_numbers) {
std::cout << num << " ";
}
std::cout << "\n";
return 0;
}
案例2: 处理图像数据
假设我们有一个简单的灰度图像,用二维向量表示。我们可以使用transform
对图像应用阈值处理:
#include <algorithm>
#include <iostream>
#include <vector>
// 简单的二维向量来模拟图像
using GrayscaleImage = std::vector<std::vector<int>>;
// 显示图像
void printImage(const GrayscaleImage& img) {
for (const auto& row : img) {
for (int pixel : row) {
std::cout << (pixel > 0 ? "■" : "□") << " ";
}
std::cout << "\n";
}
}
int main() {
// 创建一个简单的8x8灰度图像
GrayscaleImage image = {
{10, 20, 30, 40, 50, 60, 70, 80},
{15, 25, 35, 45, 55, 65, 75, 85},
{5, 15, 25, 35, 45, 55, 65, 75},
{20, 30, 40, 50, 60, 70, 80, 90},
{25, 35, 45, 55, 65, 75, 85, 95},
{30, 40, 50, 60, 70, 80, 90, 100},
{35, 45, 55, 65, 75, 85, 95, 105},
{40, 50, 60, 70, 80, 90, 100, 110}
};
std::cout << "原始图像:\n";
printImage(image);
// 对每一行应用阈值处理
for (auto& row : image) {
std::transform(row.begin(), row.end(), row.begin(),
[](int pixel) { return pixel > 50 ? 1 : 0; });
}
std::cout << "\n阈值处理后的图像:\n";
printImage(image);
return 0;
}
案例3: 函数组合
使用transform
可以轻松地组合多个函数操作,创建数据处理管道:
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <cctype>
int main() {
std::vector<std::string> names = {"john", "ALICE", "Bob", "charlie"};
std::vector<std::string> normalized(names.size());
// 将所有名字标准化: 首字母大写,其余小写
std::transform(names.begin(), names.end(), normalized.begin(),
[](std::string name) {
// 先将所有字符转为小写
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c) { return std::tolower(c); });
// 将首字母转为大写
if (!name.empty()) {
name[0] = std::toupper(name[0]);
}
return name;
});
// 打印结果
std::cout << "标准化后的名字:\n";
for (const auto& name : normalized) {
std::cout << name << "\n";
}
return 0;
}
总结
STL中的生成和变换算法为处理容器中的数据提供了强大而灵活的方式:
-
生成算法(
generate
和generate_n
)用于填充容器,非常适合初始化或重设容器的值。 -
变换算法(
transform
)允许我们以函数式编程风格对元素应用操作,既可以处理单个范围,也可以组合两个范围。 -
这些算法与 lambda 表达式结合使用时特别强大,可以创建简洁而表达力强的代码。
-
实际应用中,这些算法可以用于数据预处理、信号处理、图像处理等多种场景。
通过掌握这些算法,你可以编写出更加简洁、高效且易于理解的C++代码,尤其是在处理大型数据集时。
练习
-
使用
generate
创建一个包含斐波那契数列前10个数的向量。 -
使用
transform
将一个字符串向量中的每个字符串转换为其长度值。 -
使用双范围版本的
transform
计算两个数值向量的点积(对应元素相乘后求和)。 -
结合
generate_n
和back_inserter
向一个空向量添加10个随机数字。 -
使用
transform
实现一个简单的加密/解密函数,对字符串中的每个字符应用Caesar密码(每个字符向前或向后移动固定位数)。
进一步阅读
- C++ Reference: std::generate
- C++ Reference: std::generate_n
- C++ Reference: std::transform
- 《Effective STL》by Scott Meyers
- 《C++ Templates: The Complete Guide》by David Vandevoorde and Nicolai M. Josuttis