34、实时 C++ 编程入门:迭代器、算法与高级特性解析

实时 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++ 编程中的这些特性为开发者提供了强大的工具,能提高代码的效率、可读性和可维护性。通过合理选择和综合应用这些特性,开发者可以更好地应对各种编程场景。在实际编程中,应根据具体需求和性能要求,灵活运用这些特性,同时遵循最佳实践,以编写出高质量的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值