实时 C++ 编程入门:迭代器、算法与高级特性解析
1. 迭代器基础
在 C++ 编程中,迭代器是访问容器元素的关键工具。
begin()
和
cbegin()
是常用的迭代器获取方法。其中,
cbegin()
中的 “c” 强调该迭代器以常量方式遍历容器元素,类似
const_iterator
。部分容器成员迭代器函数(如
begin()
和
end()
)有常量和非常量两种版本,而
cbegin()
和
cend()
则仅为常量版本。
const_iterator_type const_iterator1 = cnt.begin();
const_iterator_type const_iterator2 = cnt.cbegin();
// 可通过非常量迭代器写入
*(nonconst_iterator) = 5;
// 可通过常量迭代器读取
const int n1 = *const_iterator1;
const int n2 = *const_iterator2;
// 不能通过常量迭代器写入
*(const_iterator1) = 5;
*(const_iterator2) = 5;
STL 中定义了多个迭代器类,可独立使用或作为自定义迭代器的基类,这些标准迭代器类在
<iterator>
头文件中定义。
2. STL 算法
STL 拥有大量模板算法,旨在以通用方式操作迭代器范围。多数标准算法在
<algorithm>
中定义,部分在
<numeric>
和
<memory>
中。这些算法通用性强,可与任何类型的迭代器(包括普通指针)配合使用,能将程序复杂度从用户代码转移到 STL,简化常见编码场景。
STL 算法主要分为以下几类:
1.
非修改序列操作
:如
std::all_of()
、
std::count()
、
std::for_each()
、
std::search()
等。
2.
修改序列操作
:如
std::copy()
、
std::move()
、
std::fill()
等。
3.
排序算法
。
4.
二分查找算法
:作用于已排序范围。
5.
合并操作
:作用于已排序范围。
6.
堆操作
。
7.
比较操作
:如
std::min()
、
std::max()
和
std::lexicographical_compare()
等。
以
std::for_each()
为例,其函数原型如下:
namespace std
{
template<typename iterator_type, typename function_type>
function_type for_each(iterator_type first, iterator_type last, function_type function);
}
该算法将函数参数
function
应用于范围
[first, last)
内的每个元素。以下是使用示例:
#include <algorithm>
#include <vector>
void add_five(int& elem)
{
elem += 5;
}
void do_something()
{
std::vector<int> v(3U);
v[0U] = 1;
v[1U] = 2;
v[2U] = 3;
std::for_each(v.begin(), v.end(), add_five);
}
算法的函数参数可以是具有静态链接且非子程序局部作用域的函数,也可以是支持函数调用运算符
operator()
的函数对象(仿函数)。使用仿函数会产生开销,只有当封装、数据本地化和降低复杂度等优势能弥补成本时才值得使用。
#include <algorithm>
#include <vector>
struct add_five
{
add_five() { }
void operator()(int& elem) const
{
elem += 5;
}
};
void do_something()
{
std::vector<int> v(3U);
v[0U] = 1;
v[1U] = 2;
v[2U] = 3;
std::for_each(v.begin(), v.end(), add_five());
}
此外,还可以使用 lambda 表达式作为算法的函数对象,它简洁高效,能将函数对象的全部功能集成到算法调用参数中,便于编译器优化。
#include <algorithm>
#include <vector>
void do_something()
{
std::vector<int> v(3U);
v[0U] = 1;
v[1U] = 2;
v[2U] = 3;
std::for_each(v.begin(), v.end(), [](int& elem) {
elem += 5;
});
}
结合标准算法、lambda 表达式和初始化列表,能显著提高 C++ 代码的编码效率和性能。
#include <algorithm>
#include <initializer_list>
#include <vector>
void do_something()
{
std::vector<int> v( { 1, 2, 3 } );
std::for_each(v.begin(), v.end(), [](int& elem) {
elem += 5;
});
}
3. Lambda 表达式
Lambda 表达式是一种匿名函数,有函数体但无名称。它风格优雅,与 STL 标准算法结合使用时能获得良好的优化效果。其形式为
[capture](arguments) -> return-type { body }
。
#include <cmath>
void do_something()
{
const float x = 3.0F;
const float y = 4.0F;
const float h = [&x, &y]() -> float {
return std::sqrt((x * x) + (y * y));
}();
}
在上述示例中,通过引用捕获局部变量
x
和
y
,在花括号内实现匿名函数体,末尾的括号表示函数调用。
4. 初始化列表
C++11 为 STL 添加了
std::initializer_list
,它是常量对象或值的顺序列表,元素类型须相同或可转换。使用初始化列表能方便地初始化 STL 容器,这在之前是无法实现的。
#include <vector>
using container_type = std::vector<int>;
// 使用赋值运算符初始化
container_type c1 = { 1, 2, 3 };
// 使用构造函数初始化
container_type c2( { 1, 2, 3 } );
// 统一初始化语法
container_type c3 { { 1, 2, 3 } };
函数可以接受初始化列表作为参数,且初始化列表支持迭代器。
#include <initializer_list>
#include <numeric>
constexpr std::initializer_list<int> lst { 1, 2, 3 };
const int sum = std::accumulate(lst.begin(), lst.end(), 0);
初始化列表在嵌入式系统编程中非常有用,它能以低代码开销分组对象,并将部分工作转移到编译时,便于内联优化和模板元编程。
5. 类型推断与声明
auto
和
decltype
是 C++ 中用于类型推断和声明的关键字。在 C++03 及更早版本中,
auto
用于修饰局部变量,提示编译器优先将其存储在栈上而非 CPU 寄存器。但从 C++11 开始,
auto
用于自动编译时类型推断。
// n 的类型为 int
auto n = 3;
// u 的类型为 std::uint8_t
auto u = std::uint8_t(42U);
// lst 的类型为 std::initializer_list<int>
auto lst = { 1, 2, 3 };
类型推断能降低代码复杂度,避免编写冗长的迭代器类型名。
const std::array<int, 3U> a = {{ 1, 2, 3 }};
// 使用长类型信息的 const_iterator
for(std::array<int, 3U>::const_iterator i = a.cbegin(); i != a.cend(); ++i) {
// ...
}
// 使用 auto 进行类型推断
for(auto i = a.cbegin(); i != a.cend(); ++i) {
// ...
}
// 使用通用的 std::cbegin() 和 std::cend()
for(auto i = std::cbegin(a); i != std::cend(a); ++i) {
// ...
}
decltype
用于确定已声明对象的底层类型。
int a = 1;
decltype(a) b = 2;
auto
和
decltype
在许多编程场景(尤其是通用模板编程)中能发挥重要作用。
6. 基于范围的 for 循环
C++11 引入了简化的基于范围的 for(:) 循环,用于遍历列表元素,使元素遍历更直观简洁。
#include <vector>
std::vector<char> v = { char(1), char(2), char(3) };
// 使用基于范围的 for 循环
for(char& c : v) {
c += char(0x30);
}
该循环适用于 C 风格数组、初始化列表以及具有
begin()
和
end()
函数的类型,包括所有标准库容器。尽管 C++11 及以后版本支持基于范围的 for 循环,但传统的
for(;;)
循环和
std::for_each()
算法仍然可用。
7. 元组
元组是有序对象组的泛化,如对、三元组、四元组等。在 C++11 中引入,以模板类形式实现,模板参数定义了元组对象的数量和类型。
#include <tuple>
void do_something()
{
using tuple_type = std::tuple<float, char, int>;
tuple_type t(1.23F, char('a'), 456);
// 获取元组元素
const float f = std::get<0>(t);
const char c = std::get<1>(t);
int n = std::get<2>(t);
// 获取元组元素类型
using tuple_float_type = std::tuple_element<0, tuple_type>::type;
static_assert(std::is_same<float, tuple_float_type>::value);
// 获取元组大小
int size = std::tuple_size<tuple_type>::value;
}
元组可以通过构造函数创建和初始化,也支持复制赋值、复制构造和比较操作。还可以根据元素类型检索元组元素。
#include <tuple>
void do_something()
{
using tuple_type = std::tuple<float, char, int>;
tuple_type t(1.23F, char('a'), 456);
// 根据类型获取元组元素
const float f = std::get<float>(t);
const char c = std::get<char>(t);
const int n = std::get<int>(t);
}
元组在分组对象时非常有用,且由于部分或全部元素在编译时可用,代码开销较小,适合模板设计和模板元编程。
8. 正则表达式
C++ 的
<regex>
库支持正则表达式的词法解析,但完整实现涉及大量模板和对象代码,对于大多数微控制器项目来说规模过大。不过,在相关的 PC 端程序(如文件操作、自动代码生成、专用语言解析器设计等)中,正则表达式能显著简化实现。
#include <algorithm>
#include <iostream>
#include <iterator>
#include <regex>
#include <string>
int main()
{
const std::regex rx(
std::string("([_0-9a-zA-Z]+)") +
std::string("[[:space:]]+") +
std::string("([0-9a-fA-F]+)") +
std::string("[[:space:]]+") +
std::string("([0-9]+)"));
const std::string str = "_My_Variable123 03FFB004 4";
std::match_results<std::string::const_iterator> mr;
if(std::regex_match(str, mr, rx)) {
std::copy(mr.begin(), mr.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
}
}
上述正则表达式
rx
使用 POSIX 语法,支持多种语法(默认 POSIX)。正则表达式中的括号表示捕获组,用于存储匹配结果。
regex_match()
函数用于检查输入字符串是否与正则表达式完全匹配,匹配成功时返回
true
,匹配结果存储在
mr
中。成功匹配后,匹配结果包含 N + 1 个元素(N 为捕获组数量),第 0 个结果包含整个匹配字符串。
总结
本文详细介绍了实时 C++ 编程中的多个重要概念和特性,包括迭代器、STL 算法、Lambda 表达式、初始化列表、类型推断、基于范围的 for 循环、元组和正则表达式。这些特性不仅能提高代码的可读性和可维护性,还能显著提升编程效率和性能。在实际编程中,合理运用这些特性可以更好地应对各种复杂的编程场景。
流程图:STL 算法使用流程
graph TD;
A[定义容器] --> B[选择算法类型];
B --> C{是否需要自定义函数};
C -- 是 --> D[定义函数或函数对象];
C -- 否 --> E[使用默认操作];
D --> F[调用算法并传入函数];
E --> F;
F --> G[处理容器元素];
表格:STL 算法分类
| 算法类别 | 示例函数 |
|---|---|
| 非修改序列操作 |
std::all_of()
、
std::count()
、
std::for_each()
、
std::search()
|
| 修改序列操作 |
std::copy()
、
std::move()
、
std::fill()
|
| 排序算法 | - |
| 二分查找算法 | - |
| 合并操作 | - |
| 堆操作 | - |
| 比较操作 |
std::min()
、
std::max()
、
std::lexicographical_compare()
|
实时 C++ 编程入门:迭代器、算法与高级特性解析
9. 各特性的综合应用示例
为了更好地理解上述各特性在实际编程中的综合应用,下面给出一个较为复杂的示例。该示例将使用初始化列表初始化容器,结合 Lambda 表达式和 STL 算法对容器元素进行处理,再利用元组存储处理结果,最后使用基于范围的 for 循环输出结果。
#include <algorithm>
#include <iostream>
#include <initializer_list>
#include <tuple>
#include <vector>
// 定义一个函数,使用 Lambda 表达式和 STL 算法处理容器元素
std::tuple<std::vector<int>, int> process_container(const std::vector<int>& input) {
std::vector<int> output;
int sum = 0;
// 使用 Lambda 表达式和 std::transform 算法处理元素
std::transform(input.begin(), input.end(), std::back_inserter(output), [&sum](int elem) {
int new_elem = elem * 2;
sum += new_elem;
return new_elem;
});
return std::make_tuple(output, sum);
}
int main() {
// 使用初始化列表初始化容器
std::vector<int> input = { 1, 2, 3, 4, 5 };
// 调用处理函数,得到元组结果
auto result = process_container(input);
// 从元组中获取处理后的容器和总和
const auto& output = std::get<0>(result);
const auto& total_sum = std::get<1>(result);
// 使用基于范围的 for 循环输出处理后的元素
std::cout << "Processed elements: ";
for (const auto& elem : output) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 输出总和
std::cout << "Total sum of processed elements: " << total_sum << std::endl;
return 0;
}
在这个示例中,我们首先使用初始化列表
{ 1, 2, 3, 4, 5 }
初始化了一个
std::vector<int>
容器
input
。然后定义了
process_container
函数,该函数使用
std::transform
算法和 Lambda 表达式对输入容器的每个元素进行处理(乘以 2),并计算处理后元素的总和。处理结果存储在一个元组中,元组的第一个元素是处理后的容器,第二个元素是总和。在
main
函数中,我们调用
process_container
函数得到结果元组,使用
std::get
从元组中提取处理后的容器和总和,最后使用基于范围的 for 循环输出处理后的元素,并输出总和。
10. 特性选择与性能考量
在实际编程中,选择合适的特性对于代码的性能和可维护性至关重要。以下是对各特性在不同场景下的选择建议和性能考量:
-
迭代器
:当需要遍历容器元素时,迭代器是最基本的工具。
const_iterator用于只读访问,能保证数据的安全性;而non-const_iterator可用于修改元素。在处理大型容器时,使用迭代器能避免直接操作容器元素带来的潜在风险。 -
STL 算法
:STL 算法提供了丰富的功能,能大大简化常见的编程任务。不同类型的算法适用于不同的场景,如非修改序列操作适用于统计、查找等任务,修改序列操作适用于元素复制、移动等任务。在使用算法时,应根据具体需求选择合适的算法,避免不必要的开销。例如,如果只需要统计容器中满足某个条件的元素数量,使用
std::count_if比手动遍历容器更高效。 - Lambda 表达式 :Lambda 表达式简洁高效,与 STL 算法结合使用时能提升代码的可读性和可维护性。当需要定义简单的函数对象时,Lambda 表达式是首选。但如果函数逻辑复杂,建议使用普通函数或函数对象,以提高代码的可维护性。
- 初始化列表 :初始化列表能方便地初始化容器,尤其适用于在定义容器时直接指定初始值。在嵌入式系统编程中,由于初始化列表的元素可以在编译时确定,能减少运行时开销,提高性能。
-
类型推断(
auto和decltype) :类型推断能减少代码中的类型声明,提高代码的简洁性。在处理复杂类型(如迭代器类型)时,使用auto能避免冗长的类型名称。但在某些情况下,显式声明类型可能更有助于代码的理解,因此应根据具体情况合理使用。 -
基于范围的 for 循环
:基于范围的 for 循环能简化容器元素的遍历,使代码更直观。但它只能按顺序遍历容器,对于需要随机访问或反向遍历的情况,传统的
for循环或迭代器更合适。 - 元组 :元组适用于将多个不同类型的对象组合在一起,尤其在需要返回多个值时非常方便。由于元组的元素类型和数量在编译时确定,能进行较好的优化,减少运行时开销。
- 正则表达式 :正则表达式在处理字符串匹配和解析时非常强大,但由于其实现涉及大量模板和对象代码,对于资源受限的系统(如微控制器)可能不适用。在 PC 端程序中,合理使用正则表达式能显著简化字符串处理任务。
11. 特性使用的最佳实践
为了更好地利用这些特性,以下是一些最佳实践建议:
- 代码复用 :尽量将常用的功能封装成函数或函数对象,以便在不同的地方复用。例如,将处理容器元素的逻辑封装成一个函数,在多个地方调用该函数,能提高代码的可维护性和复用性。
-
错误处理
:在使用 STL 算法和其他特性时,要注意错误处理。例如,在使用
std::regex_match时,应检查返回值,确保匹配成功后再处理匹配结果。 - 注释和文档 :对于复杂的代码逻辑,尤其是使用 Lambda 表达式和元组时,应添加适当的注释和文档,解释代码的功能和使用方法,以便其他开发者理解和维护代码。
- 性能优化 :在性能敏感的场景下,应进行性能测试,选择最适合的特性和算法。例如,在处理大量数据时,使用高效的排序算法能显著提高程序的运行速度。
流程图:特性选择流程
graph TD;
A[明确编程任务] --> B{是否涉及容器操作};
B -- 是 --> C{是否需要遍历容器};
C -- 是 --> D{是否需要修改元素};
D -- 是 --> E[使用 non-const 迭代器或修改序列算法];
D -- 否 --> F[使用 const 迭代器或非修改序列算法];
C -- 否 --> G{是否需要组合多个值};
G -- 是 --> H[使用元组];
G -- 否 --> I{是否需要处理字符串匹配};
I -- 是 --> J[使用正则表达式];
I -- 否 --> K[根据具体需求选择其他特性];
B -- 否 --> K;
表格:特性对比
| 特性 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 迭代器 | 基本的容器访问工具,可灵活控制遍历 | 代码相对复杂 | 需要精确控制容器遍历 |
| STL 算法 | 功能丰富,可简化常见编程任务 | 部分算法可能有一定开销 | 处理常见的容器操作任务 |
| Lambda 表达式 | 简洁高效,与 STL 算法结合好 | 复杂逻辑时可读性差 | 定义简单函数对象 |
| 初始化列表 | 方便容器初始化,编译时确定元素 | 只能用于初始化 | 容器初始化 |
类型推断(
auto
和
decltype
)
| 减少类型声明,提高代码简洁性 | 可能降低代码可读性 | 处理复杂类型 |
| 基于范围的 for 循环 | 简化容器遍历,代码直观 | 只能顺序遍历 | 简单的顺序遍历容器 |
| 元组 | 组合多个不同类型对象,编译时优化 |
元素访问需使用
std::get
| 需要返回多个值 |
| 正则表达式 | 强大的字符串匹配和解析能力 | 实现复杂,开销大 | 处理字符串匹配和解析 |
总结
实时 C++ 编程中的这些特性为开发者提供了强大的工具,能提高代码的效率、可读性和可维护性。通过合理选择和综合应用这些特性,开发者可以更好地应对各种编程场景。在实际编程中,应根据具体需求和性能要求,灵活运用这些特性,同时遵循最佳实践,以编写出高质量的代码。
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



