Skip to content

Arduino - String 对象

在Arduino编程中,您可以通过两种主要方式处理文本:C风格的字符数组(也称为C字符串)和String对象。本教程重点介绍String对象,这是一种更用户友好但资源密集型的文本处理方式。

在面向对象编程(OOP)中,对象是一种将数据(属性)和操作该数据的功能(方法)捆绑在一起的结构。Arduino String对象将字符序列作为其数据,并提供一套丰富的方法来操作该文本,例如转换为大写、替换子字符串等。

尽管String对象功能强大,但其便利性是有代价的,尤其是在内存受限的微控制器(如Arduino Uno)上。它使用动态内存分配,这可能导致一个称为堆碎片的问题。我们将在下面探讨这一点和最佳实践。

实际示例:一个简单的串行命令解析器

Section titled “实际示例:一个简单的串行命令解析器”

让我们创建一个草图,它侦听来自串行监视器的简单命令,如LED ON或LED OFF,以演示String对象的功能。

// 板载LED的引脚
const int ledPin = LED_BUILTIN;
// 一个String对象,用于存储来自串口的传入数据
String command;
void setup() {
// 以每秒9600位的速度初始化串行通信:
Serial.begin(9600);
while (!Serial) {
; // 等待串口连接。仅对原生USB端口需要
}
// 将LED引脚配置为输出
pinMode(ledPin, OUTPUT);
// 为command String预分配内存以减少堆碎片。
// 这是一个至关重要的最佳实践!
command.reserve(32);
Serial.println("Arduino Command Parser Ready");
Serial.println("Enter 'LED ON' or 'LED OFF'");
}
void loop() {
// 检查串口是否有可读取的数据
if (Serial.available() > 0) {
// 读取传入字节,直到收到换行符
command = Serial.readStringUntil('\n');
// 移除任何前导/尾随空白字符
command.trim();
// 将命令转换为大写以实现不区分大小写的匹配
command.toUpperCase();
Serial.print("Received command: ");
Serial.println(command);
// 处理命令
if (command == "LED ON") {
Serial.println("Executing: Turning LED ON");
digitalWrite(ledPin, HIGH);
} else if (command == "LED OFF") {
Serial.println("Executing: Turning LED OFF");
digitalWrite(ledPin, LOW);
} else {
Serial.println("Error: Unknown command.");
printHelp();
}
// (可选)获取并打印最后一条命令的长度
Serial.print("Command length was: ");
Serial.println(command.length());
// 清空字符串以便接收下一条命令
command = "";
}
}
// 一个帮助函数,用于打印可用命令
void printHelp() {
Serial.println("Available commands: 'LED ON', 'LED OFF'");
}

上传草图后,打开串行监视器(设置为9600波特率和“换行符”行尾)。如果您键入led on并按回车键,您将看到:

Received command: LED ON
Executing: Turning LED ON
Command length was: 6
  • reserve(size):一种最佳实践方法,用于在内存中预先分配特定大小的缓冲区。这有助于通过一次性找到一个内存块来防止堆碎片,而不是不断请求新的、不同大小的内存块。
  • readStringUntil(char):从串行缓冲区读取字符到String中,直到找到特定字符(在本例中为换行符\n)。
  • trim():从String的开头和结尾删除任何空白字符(如空格、制表符)。
  • toUpperCase():将String中所有字母字符转换为大写形式。这对于实现不区分大小写的命令解析非常有用。
  • length():返回String中的字符数,不包括空终止符。
  • 比较(==):String对象可以使用==等运算符直接比较,这比C风格字符串使用的strcmp()函数简单得多。

String的两难选择:何时使用与何时不使用

Section titled “String的两难选择:何时使用与何时不使用”
  • 易用性:丰富的内置方法使文本操作直观,并且需要的代码量更少。
  • 简洁性:连接(+)和比较(==)等操作简单明了。
  • 自动内存管理:您无需手动管理缓冲区大小(尽管这也是其最大的弱点)。

缺点与危险(尤其是在Uno/Nano等AVR开发板上)

Section titled “缺点与危险(尤其是在Uno/Nano等AVR开发板上)”
  • 堆碎片:重复创建和销毁String对象可能会在Arduino有限的RAM(SRAM)中留下小的、不可用的空隙。最终,系统可能无法找到足够大的连续内存块用于新分配,导致程序崩溃或行为异常,即使技术上总内存是足够的。
  • 高内存开销:String对象比C字符串占用更多内存。
  • 隐藏的复杂性:String对象的简单性可能会隐藏性能密集型操作,使得调试性能问题变得困难。

为了编写健壮且稳定的Arduino代码,请遵循以下准则:

  1. 避免在循环中使用String:如果可以避免,切勿在主loop()函数内部重复创建或修改String对象。这是导致堆碎片的主要原因。
  2. 使用reserve():如果您必须使用String对象,请在setup()中使用reserve()为其预分配一个固定且充足的内存量。
  3. 为了可靠性,首选C风格字符串:对于任务关键型或长期运行的应用程序,学习使用空终止字符数组(char myStr[32];)。它们为您提供了对内存的完全控制,并且不易受碎片影响。您可以使用标准C库函数(如来自<string.h>库的strcpy()、strcat()和strcmp())来操作它们。
  4. 考虑开发板选择:在内存显著更多的更强大的32位开发板(如ESP32或Arduino Due)上,与String对象相关的风险较低,但良好的内存管理仍然是一项宝贵的技能。