文章目录
前言
最近在玩Arduino,自上次发现Arduino可以用Template,能使用高级宏后,这次发现Arduino竟可以使用C++中的一些STL容器,这属实令人震惊。起因是我打算做一个动态的数组,但是手动实现一些操作属实麻烦,于是看看是否有一些简便的方法,最好是Arduino本身就已经集成的东西。经过一阵查找资料,发现大家似乎一直对Arduino比较鄙夷,但还是有国外大佬提示Arduino中已经自带这部分内容,只不过是部分数据类型需要手动包含头文件。(即使官方自带,仍然有很多人造轮子,是故意的还是不小心)当真正上手的时候才发现Arduino是多么强大,尽管不像esptool那么精细,但是该有的基本上都有,还支持C++的语法,实在是太香了。
Arduino 框架为嵌入式开发提供了简单易用的 API,使得初学者可以快速上手。然而,Arduino 语言(基于 C++)的强大之处远不止于此。C++ 的许多高级特性可以帮助我们编写更高效、更健壮、更易于维护的嵌入式代码。
在 ESP32-S3 这样的高性能微控制器上,充分利用 C++ 的高级特性尤为重要。这些特性可以帮助我们:
- 提高代码效率: 利用模板、内联函数、constexpr 等特性,减少代码冗余,提高运行速度。
- 增强代码可读性和可维护性: 使用面向对象编程(OOP)、智能指针、命名空间等特性,使代码结构更清晰,更易于理解和修改。
- 减少内存占用: 使用 RAII(资源获取即初始化)、移动语义等特性,更有效地管理内存资源。
- 提高代码安全性: 使用 const、引用、异常处理等特性,减少潜在的错误。
- 更好地利用 ESP32-S3 的硬件资源: 使用 C++ 的底层特性,更直接地访问硬件。
在嵌入式开发中,数据管理是不可或缺的一部分。Arduino 框架虽然提供了基本的数据结构和函数,但在处理复杂的数据时,往往显得力不从心。C++ 的标准模板库(STL,Standard Template Library)提供了一组强大的、通用的数据结构和算法,可以帮助开发者更高效、更安全地管理数据。本文将详细介绍如何在 Arduino 环境下使用 STL 容器,并通过实际示例展示它们的应用。
如果你对其他的特性感兴趣,我可以在后续文章中更新。
通过本文的学习,你将能够:
- 了解 STL 容器的基本概念和常用容器。
- 掌握在 Arduino 中使用 STL 容器的方法。
- 学会如何选择合适的容器来解决实际问题。
- 优化内存使用,提高代码效率。
注意: 本文假设读者已经具备一定的 Arduino 和 C++ 基础知识。
1. STL 容器概述
STL 是 C++ 标准库的一部分,提供了多种数据结构(容器)和算法,帮助开发者高效地管理和操作数据。常见的 STL 容器包括:
- 序列容器: 如
vector
、array
、list
、deque
等。 - 关联容器: 如
set
、map
、multiset
、multimap
等。 - 容器适配器: 如
stack
、queue
、priority_queue
等。 - 其他容器: 如
string
、bitset
等。
2. 在 Arduino 中使用 STL 容器
Arduino 使用的是 C++ 编程语言,因此可以直接使用 STL 容器。然而,由于 Arduino 的资源有限(尤其是内存),在选择和使用 STL 容器时需要格外小心。
2.1. 启用 STL
Arduino IDE 默认不启用 STL。如果你的项目需要使用 STL 容器,可以在代码中添加以下行来启用 STL:
#include <Arduino.h>
using namespace std;
或者在 ino
文件中添加:
#include <algorithm>
#include <vector>
#include <string>
// 其他需要的 STL 头文件
2.2. 内存管理
Arduino 的内存资源有限,尤其是 RAM。因此,在使用 STL 容器时,应尽量避免使用过多的内存。例如:
- 使用
array
代替vector
: 如果数据的大小是固定的,使用array
可以避免动态内存分配。 - 使用
string
而不是 C 风格字符串:string
更安全、更易于使用。 - 避免过多的数据存储: 确保你的数据结构不会占用过多的 RAM。
3. 常用 STL 容器
3.1. vector
vector
是 STL 中最常用的容器之一。它是一个动态数组,可以自动调整大小以适应元素的数量。
特点:
- 动态大小: 可以随时添加或删除元素。
- 高效的随机访问: 元素可以通过索引快速访问。
- 内存管理: 自动管理内存,避免了手动分配和释放内存的麻烦。
示例:
#include <vector>
void setup() {
Serial.begin(115200);
// 创建一个 vector,存储传感器读数
std::vector<int> sensorReadings;
// 添加数据
sensorReadings.push_back(10);
sensorReadings.push_back(20);
sensorReadings.push_back(30);
// 访问数据
for (size_t i = 0; i < sensorReadings.size(); ++i) {
Serial.print("Reading ");
Serial.print(i);
Serial.print(": ");
Serial.println(sensorReadings[i]);
}
}
void loop() {
// 空循环
}
3.2. array
array
是一个固定大小的数组容器。与 C 风格数组相比,它提供了更多的安全性和便利性。
特点:
- 固定大小: 在编译时确定大小,无法动态调整。
- 内存安全: 避免了缓冲区溢出的风险。
- 高效: 由于大小固定,访问速度非常快。
示例:
#include <array>
void setup() {
Serial.begin(115200);
// 创建一个固定大小的 array
std::array<int, 3> sensorReadings = {
10, 20, 30};
// 访问数据
for (size_t i = 0; i < sensorReadings.size(); ++i) {
Serial.print("Reading ");
Serial.print(i);
Serial.print(": ");
Serial.println(sensorReadings[i]);
}
}
void loop() {
// 空循环
}
3.3. string
string
是 STL 中用于处理字符串的容器。它提供了丰富的功能,例如拼接、查找、替换等。
特点:
- 动态大小: 可以自动调整以适应字符串的长度。
- 安全性: 避免了 C 风格字符串的许多安全隐患。
- 便利性: 提供了许多实用的函数,简化了字符串操作。
示例:
#include <string>
void setup() {
Serial.begin(115200);
// 创建一个 string
std::string message = "Hello, ESP32-S3! ";
// 拼接字符串
message += "Welcome to STL containers!";
// 输出结果
Serial.println(message);
}
void loop() {
// 空循环
}
3.4. list
list
是一个双向链表容器。它允许在任何位置高效地插入和删除元素。
特点:
- 高效的插入和删除: 可以在常数时间内完成。
- 双向访问: 提供了前向和后向迭代器。
- 内存分配: 每个元素独立分配内存,减少内存碎片。
示例:
#include <list>
void setup() {
Serial.begin<