24、C++ 标准模板库(STL)实用指南

C++ 标准模板库(STL)实用指南

1. STL 容器

STL 容器在 C++ 编程中扮演着重要角色,能极大地提升程序的组织性和数据的局部性。以 std::vector 为例,可使用 push_back() 成员函数将元素添加到向量末尾,还能使用 size() at() back() 等方法。

#include <vector>

void do_something() {
    std::vector<int> v;
    v.push_back(4); // 将值为 4 的元素添加到向量末尾
    size_t s = v.size(); // 获取向量的大小
    int element = v.at(0); // 获取指定位置的元素
    int last = v.back(); // 获取向量的最后一个元素
}

容器支持多种创建和赋值方式,如复制构造、从迭代器序列创建以及复制赋值。其成员类型定义用于定义常见成员类型,如 std::vector size_type 。常见成员类型还包括迭代器类型、指针类型、引用类型和值类型。

操作类型 描述
复制构造 使用已有的容器创建新容器
从迭代器序列创建 根据迭代器范围创建容器
复制赋值 将一个容器的值赋给另一个容器

2. STL 迭代器

迭代器是指针的泛化,用于指向顺序容器中的元素,可操作标准 STL 容器的元素。每个标准 STL 容器都提供专门的迭代器类型和标准化的迭代器函数,如 begin() end()

#include <vector>

void do_something() {
    std::vector<int> v(3U);
    v[0U] = 1;
    v[1U] = 2;
    v[2U] = 3;
    for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        *it += 5; // 给每个元素加 5
    }
    // 现在 v 为 (6, 7, 8)
}

迭代器对 [First; Last) 用于界定序列范围,其中 First 指向序列的第一个元素, Last 指向最后一个元素的下一个位置。STL 的标准算法遵循此约定,确保与 STL 和其他代码的兼容性。

graph LR
    A[开始] --> B[初始化容器]
    B --> C[获取迭代器范围]
    C --> D[遍历元素]
    D --> E[操作元素]
    E --> F{是否到达末尾}
    F -- 否 --> D
    F -- 是 --> G[结束]

迭代器支持递增操作,部分支持递减操作。一般来说,前置递增和递减形式比后置形式更高效。所有 STL 迭代器使用解引用运算符 * 或成员选择运算符 -> 访问元素。C++ 有多种迭代器类别,如前向迭代器、双向迭代器和随机访问迭代器。

常量迭代器和非常量迭代器有明显区别,常量迭代器只能进行只读访问,非常量迭代器可读写容器元素。

container_type::iterator nonconst_iterator1 = cnt.begin();
container_type::const_iterator const_iterator2 = cnt.begin();
container_type::const_iterator const_iterator3 = cnt.cbegin();
*nonconst_iterator1 = 1; // 合法
*const_iterator2 = 2; // 错误!
*const_iterator3 = 3; // 错误!

3. STL 算法

STL 拥有大量模板算法,主要定义在 <algorithm> <numeric> <memory> 中。这些算法高度通用,可与任何类型的迭代器甚至普通指针一起使用,能将程序复杂性从用户代码转移到 STL,简化常见编码场景。

算法分为多个类别:
- 非修改序列操作 :如 std::all_of() std::count() std::for_each() std::search() 等。
- 修改序列操作 :如 std::copy() std::move() std::fill() 等。
- 排序算法
- 二分查找算法 :在有序范围内操作。
- 合并操作 :作用于有序范围。
- 堆操作
- 比较操作 :如 std::min() std::max() std::lexicographical_compare() 等。

std::for_each() 为例,其函数原型如下:

template<typename iterator_type, typename function_type>
function_type std::for_each(iterator_type first, iterator_type last, function_type function);

该算法将函数参数应用于 [first; last) 范围内的每个元素。

#include <algorithm>
#include <vector>

namespace {
    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);
    // 现在 v 为 (6, 7, 8)
}

算法的函数参数可以是具有静态链接且非子程序局部作用域的函数,也可以是函子(函数对象),还能使用 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;
    });
    // 现在 v 为 (6, 7, 8)
}

4. Lambda 表达式

Lambda 表达式是一种匿名函数,具有函数体但没有名称。它风格简洁,与 STL 标准算法结合使用时能很好地进行优化。

C++ 的 lambda 表达式形式如下:

[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));
    }();
    // h 的值为 5.0F
}

在这个例子中,局部变量 x y 通过引用捕获,匿名函数的主体在花括号内实现,最后通过尾随的括号调用该函数。

5. 初始化列表

C++11 向 STL 中添加了 std::initializer_list ,它是一个常量对象或值的顺序列表,其中的元素必须类型相同或可类型转换。STL 容器可以方便地使用初始化列表进行初始化。

// 使用赋值运算符初始化
container c1 = { 1, 2, 3 };
// 使用构造函数初始化
container c2( { 1, 2, 3 } );
// 使用统一初始化语法初始化
container c3 {{ 1, 2, 3 }};

函数可以接受初始化列表作为参数,并且初始化列表支持迭代器。

#include <initializer_list>
#include <numeric>

constexpr std::initializer_list<int> lst {1, 2, 3};
const int sum = std::accumulate(std::begin(lst), std::end(lst), 0);

初始化列表在嵌入式系统编程中非常有用,因为它们像元组一样,能以较低的代码开销对对象进行分组。由于其值可能是编译时常量,因此适合内联和模板元编程。

6. 类型推断

C++11 中 auto 关键字的含义发生了巨大变化,从早期作为局部变量的限定符,变为用于自动编译时类型推断。

auto n = 3; // n 是 int 类型
auto u = std::uint8_t(3U); // u 是 std::uint8_t 类型
auto collection { 1, 2, 3 }; // collection 的类型是 std::initializer_list<int>

auto 还能减少代码的冗长性,例如在使用迭代器时:

// 传统方式
for(std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)

// 使用 auto 进行类型推断
for(auto it = v.begin(); it != v.end(); ++it)

// 结合 STL 的通用版本的 std::begin() 和 std::end()
for(auto it = std::begin(v); it != std::end(v); ++it)

这种构造在泛型模板编程等场景中非常有用。

7. 范围 for 循环

C++11 引入了简化的范围 for 循环 for(:) ,用于遍历列表中的元素。

std::vector<char> v( {1, 2, 3} );
for(char& c : v) {
    c += static_cast<char>(0x30);
}

这个简化的循环意味着遍历 v 中的每个字符,并给每个字符加上 0x30 。传统的 for(;;) 循环和 for_each() 算法仍然可用,但范围 for 循环可能更方便和简洁。范围 for 循环适用于 C 风格数组、初始化列表以及任何具有 begin() end() 函数的类型,包括所有标准库容器。

8. 元组

元组是有序对象组的泛化,如对、三元组、四元组等。C++11 引入了元组,它以模板类的形式实现,模板参数定义了元组对象的数量和类型。

#include <tuple>

typedef std::tuple<float, char, int> tuple_type;

void do_something() {
    tuple_type t(1.23F, 'a', 123); // 创建并初始化元组
    char c = std::get<1>(t); // 获取元组的第 1 个元素
    int n = std::get<2>(t); // 获取元组的第 2 个元素
    std::tuple_element<0, tuple_type>::type val = std::get<0>(t); // 获取元组的第 0 个元素的类型
    int size = std::tuple_size<tuple_type>::value; // 获取元组的大小
}

元组支持复制赋值和复制构造,使用成员逐个赋值。可以使用 std::make_tuple() 来创建元组,并且元组可以进行比较,比较函数使用关系运算符进行逐对比较。

#include <string>
#include <tuple>

void do_something() {
    std::tuple<int, std::string> t1(123, "identical");
    std::tuple<int, std::string> t2 = t1; // 复制赋值
    std::tuple<int, std::string> t3(t1); // 复制构造
    bool result;
    result = (t1 == t2); // 比较元组
    result = (t1 == t3);
    std::get<0>(t2) += 1; // 修改元组元素
    result = (t2 > t1);
    std::get<1>(t3).at(0U) = 'x';
    result = (t3 > t1);
}

元组能将对象集合组合在一个表示中,同时代码开销最小,适合模板设计和模板元编程。

9. 正则表达式

C++ 中对正则表达式的词法解析支持在 <regex> 库中实现。但完整实现 <regex> 涉及大量模板和对象代码,对于大多数微控制器项目来说规模太大。不过,微控制器编程通常涉及相关的基于 PC 的程序和实用工具,正则表达式的词法解析可以大大简化这些程序的实现,因此微控制器程序员应具备基本的 <regex> 能力。

考虑一个用于解析由三个子字符串组成的复合字符串的正则表达式,第一个子字符串是包含下划线的字母数字名称,第二个是十六进制数字,第三个是十进制无符号整数。

#include <algorithm>
#include <iterator>
#include <iostream>
#include <string>
#include <regex>

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"));
    }
    return 0;
}

这个正则表达式使用 POSIX 语法, <regex> 库支持多种语法,POSIX 是默认语法。正则表达式定义中的括号表示捕获组,用于存储匹配的表达式。在示例程序中,如果字符串匹配正则表达式,则将匹配结果输出到控制台。

10. 总结与应用建议

10.1 各特性总结
特性 描述 优点 适用场景
STL 容器 提供多种数据结构,如 std::vector 等,支持复制构造、赋值等操作 提升程序组织性和数据局部性,支持多态 处理各种数据集合,如存储对象列表、动态数组等
STL 迭代器 泛化的指针,用于访问容器元素,有多种类型 统一的元素访问方式,与 STL 算法配合良好 遍历容器元素,实现算法操作
STL 算法 大量模板算法,可操作迭代器范围 简化常见编码任务,提高代码复用性 排序、查找、复制等数据处理操作
Lambda 表达式 匿名函数,与 STL 算法结合使用 简洁高效,便于编译器优化 临时函数需求,如算法的操作函数
初始化列表 std::initializer_list 用于容器初始化 方便快捷的初始化方式 容器初始化,传递一组常量值
类型推断 auto 关键字实现自动类型推断 减少代码冗长性,提高代码可读性 复杂类型声明,迭代器类型等
范围 for 循环 for(:) 语法遍历元素 代码简洁,易于使用 简单的容器元素遍历
元组 有序对象组的泛化,模板类实现 组合不同类型对象,代码开销小 函数返回多个值,存储不同类型数据
正则表达式 <regex> 库实现词法解析 强大的字符串匹配和解析能力 文本处理、数据验证等
10.2 应用建议
  • 选择合适的容器 :根据数据特点和操作需求选择容器。如果需要动态数组,可使用 std::vector ;如果需要频繁插入和删除元素,可考虑 std::list
  • 合理使用迭代器 :根据容器类型和操作需求选择迭代器类型。对于顺序访问,前向迭代器可能足够;对于双向访问,使用双向迭代器;对于随机访问,使用随机访问迭代器。
  • 利用 STL 算法 :优先使用 STL 算法,避免重复造轮子。例如,使用 std::sort() 进行排序,使用 std::find() 进行查找。
  • 灵活运用 Lambda 表达式 :在需要临时函数的场景中,使用 Lambda 表达式可以使代码更简洁。例如,在 std::for_each() 中使用 Lambda 表达式进行元素操作。
  • 简化代码使用类型推断和范围 for 循环 :使用 auto 关键字减少类型声明的冗长性,使用范围 for 循环简化元素遍历。
  • 使用元组组合数据 :当需要返回多个值或存储不同类型的数据时,使用元组可以提高代码的可读性和可维护性。
  • 谨慎使用正则表达式 :由于正则表达式的实现可能较大,对于资源受限的系统要谨慎使用。但在文本处理和数据验证等场景中,正则表达式可以大大简化代码。

11. 综合示例

下面是一个综合使用上述特性的示例代码,展示了如何使用 STL 容器、算法、Lambda 表达式、初始化列表等特性来处理数据。

#include <iostream>
#include <vector>
#include <algorithm>
#include <tuple>
#include <regex>

// 定义一个函数,使用 Lambda 表达式和 STL 算法处理向量
void processVector() {
    // 使用初始化列表初始化向量
    std::vector<int> v = { 1, 2, 3, 4, 5 };

    // 使用 Lambda 表达式和 std::for_each 对每个元素加 5
    std::for_each(v.begin(), v.end(), [](int& elem) {
        elem += 5;
    });

    // 使用范围 for 循环输出处理后的向量
    for (auto elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;
}

// 定义一个函数,使用元组返回多个值
std::tuple<int, double, std::string> getTuple() {
    return std::make_tuple(10, 3.14, "Hello");
}

// 定义一个函数,使用正则表达式匹配字符串
void matchString() {
    const std::regex rx("([0-9]+)-([a-zA-Z]+)");
    const std::string str = "123-abc";
    std::smatch mr;
    if (std::regex_match(str, mr, rx)) {
        std::cout << "Match found:" << std::endl;
        for (auto match : mr) {
            std::cout << match << std::endl;
        }
    } else {
        std::cout << "No match found." << std::endl;
    }
}

int main() {
    // 处理向量
    processVector();

    // 获取元组并输出元素
    auto t = getTuple();
    std::cout << "Tuple elements:" << std::endl;
    std::cout << std::get<0>(t) << std::endl;
    std::cout << std::get<1>(t) << std::endl;
    std::cout << std::get<2>(t) << std::endl;

    // 匹配字符串
    matchString();

    return 0;
}

12. 总结

通过本文的介绍,我们了解了 C++ 标准模板库(STL)的多个重要特性,包括容器、迭代器、算法、Lambda 表达式、初始化列表、类型推断、范围 for 循环、元组和正则表达式。这些特性为 C++ 编程提供了强大的工具和便捷的语法,能够提高代码的效率、可读性和可维护性。

在实际编程中,我们应该根据具体的需求和场景,合理选择和运用这些特性。同时,要注意不同特性之间的配合使用,以达到最佳的编程效果。希望本文能够帮助你更好地掌握 C++ STL 的使用,提升你的编程能力。

graph LR
    A[开始] --> B[选择合适的容器]
    B --> C[使用迭代器访问容器]
    C --> D[运用 STL 算法处理数据]
    D --> E{是否需要临时函数}
    E -- 是 --> F[使用 Lambda 表达式]
    E -- 否 --> G[继续处理]
    F --> G
    G --> H{是否需要初始化容器}
    H -- 是 --> I[使用初始化列表]
    H -- 否 --> J[继续处理]
    I --> J
    J --> K{是否需要简化类型声明}
    K -- 是 --> L[使用类型推断]
    K -- 否 --> M[继续处理]
    L --> M
    M --> N{是否需要简单遍历元素}
    N -- 是 --> O[使用范围 for 循环]
    N -- 否 --> P[继续处理]
    O --> P
    P --> Q{是否需要组合不同类型数据}
    Q -- 是 --> R[使用元组]
    Q -- 否 --> S[继续处理]
    R --> S
    S --> T{是否需要字符串匹配和解析}
    T -- 是 --> U[使用正则表达式]
    T -- 否 --> V[结束]
    U --> V

这个流程图展示了在实际编程中,根据不同的需求和场景,如何选择和运用 C++ STL 的各个特性。从选择合适的容器开始,逐步根据具体情况决定是否使用 Lambda 表达式、初始化列表、类型推断等特性,最终完成编程任务。

计及源荷不确定性的综合能源生产单元运行调度与容量配置优化研究(Matlab代码实现)内容概要:本文围绕“计及源荷不确定性的综合能源生产单元运行调度与容量配置优化”展开研究,利用Matlab代码实现相关模型的构建与仿真。研究重点在于综合能源系统中多能耦合特性以及风、光等可再生能源出力和负荷需求的不确定性,通过鲁棒优化、场景生成(如Copula方法)、两阶段优化等手段,实现对能源生产单元的运行调度与容量配置的协同优化,旨在提高系统经济性、可靠性和可再生能源消纳能力。文中提及多种优化算法(如BFO、CPO、PSO等)在调度与预测中的应用,并强调了模型在实际能源系统规划与运行中的参考价值。; 适合人群:具备一定电力系统、能源系统或优化理论基础的研究生、科研人员及工程技术人员,熟悉Matlab编程和基本优化工具(如Yalmip)。; 使用场景及目标:①用于学习和复现综合能源系统中考虑不确定性的优化调度与容量配置方法;②为含高比例可再生能源的微电网、区域能源系统规划设计提供模型参考和技术支持;③开展学术研究,如撰写论文、课题申报时的技术方案借鉴。; 阅读建议:建议结合文中提到的Matlab代码和网盘资料,先理解基础模型(如功率平衡、设备模型),再逐步深入不确定性建模与优化求解过程,注意区分鲁棒优化、随机优化与分布鲁棒优化的适用场景,并尝试复现关键案例以加深理解。
内容概要:本文系统分析了DesignData(设计数据)的存储结构,围绕其形态多元化、版本关联性强、读写特性差异化等核心特性,提出了灵活性、版本化、高效性、一致性和可扩展性五大设计原则。文章深入剖析了三类主流存储方案:关系型数据库适用于结构化元信息存储,具备强一致性与高效查询能力;文档型数据库适配半结构化数据,支持动态字段扩展与嵌套结构;对象存储结合元数据索引则有效应对非结构化大文件的存储需求,具备高扩展性与低成本优势。同时,文章从版本管理、性能优化和数据安全三个关键维度提出设计要点,建议采用全量与增量结合的版本策略、索引与缓存优化性能、并通过权限控制、MD5校验和备份机制保障数据安全。最后提出按数据形态分层存储的核心结论,并针对不同规模团队给出实践建议。; 适合人群:从事工业设计、UI/UX设计、工程设计等领域数字化系统开发的技术人员,以及负责设计数据管理系统架构设计的中高级工程师和系统架构师。; 使用场景及目标:①为设计数据管理系统选型提供依据,合理选择或组合使用关系型数据库、文档型数据库与对象存储;②构建支持版本追溯、高性能访问、安全可控的DesignData存储体系;③解决多用户协作、大文件存储、历史版本管理等实际业务挑战。; 阅读建议:此资源以实际应用场景为导向,结合具体数据库类型和表结构设计进行讲解,建议读者结合自身业务数据特征,对比分析不同存储方案的适用边界,并在系统设计中综合考虑成本、性能与可维护性之间的平衡。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值