HOWTO · Arduino
在 Arduino 中拆分字串
可以使用 Arduino 中的 substring() 函式拆分字串。
本頁內容
Arduino 沒有單一的 String 方法可回傳包含所有欄位的陣列。已知擷取範圍時,可以使用 substring();分隔符位置不固定時,可以使用 indexOf() 尋找;也可以使用 C 函式 strtok() 分割可寫入的字元陣列。手動解析迴圈可讓你自行控制空欄位與多種分隔符的處理方式。請依據位置是否已知、輸入資料是否可以修改,以及分隔符應如何處理來選擇方法。
以下完整程式碼使用 Arduino 的 String 和 Serial API。未定義 ARDUINO 時,小型主機端相容層可讓同一份程式碼以標準 C++ 執行;在開發板上,這些 API 則由 Arduino 核心提供。此處列出的序列輸出由程式碼產生。
在 Arduino 中使用 substring() 依索引擷取字串
已知要擷取的範圍時,請使用 substring(begin, end)。起始索引包含在結果中,結束索引則不包含。因此,對 "Hello,World" 而言,索引 0 到 4 的字元組成 Hello,結束引數應設為 5。只有一個引數的呼叫,例如 substring(begin),會從指定索引擷取到字串結尾。
請執行以下程式碼:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#ifdef ARDUINO
#include <Arduino.h>
#else
#include <iostream>
#include <string>
class String {
public:
String(const char *value) : value_(value) {}
int indexOf(char target) const {
const auto position = value_.find(target);
return position == std::string::npos ? -1 : static_cast<int>(position);
}
int indexOf(char target, unsigned int fromIndex) const {
const auto position = value_.find(target, fromIndex);
return position == std::string::npos ? -1 : static_cast<int>(position);
}
int lastIndexOf(char target) const {
const auto position = value_.rfind(target);
return position == std::string::npos ? -1 : static_cast<int>(position);
}
String substring(unsigned int begin, unsigned int end) const {
return String(value_.substr(begin, end - begin).c_str());
}
String substring(unsigned int begin) const {
return String(value_.substr(begin).c_str());
}
const char *c_str() const { return value_.c_str(); }
private:
std::string value_;
};
struct HostSerial {
void begin(unsigned long) {}
void println(const String &value) { std::cout << value.c_str() << '\n'; }
void println(const char *value) { std::cout << value << '\n'; }
};
HostSerial Serial;
#endif
#include <cstring>
void setup() {
Serial.begin(9600);
String greeting = "Hello,World";
Serial.println(greeting.substring(0, 5));
String record = "sensor1:value1";
int separator = record.indexOf(':');
if (separator != -1) {
Serial.println(record.substring(0, separator));
}
String recordWithoutColon = "sensor1";
int absentSeparator = recordWithoutColon.indexOf(':');
if (absentSeparator == -1) {
Serial.println("delimiter not found");
} else {
Serial.println(recordWithoutColon.substring(0, absentSeparator));
}
String lines = "hello world\nGreetings\nPeople";
int finalNewline = lines.lastIndexOf('\n');
String finalLine = finalNewline == -1
? lines
: lines.substring(finalNewline + 1);
Serial.println(finalLine);
String oneLine = "single line";
int noNewline = oneLine.lastIndexOf('\n');
String onlyLine = noNewline == -1 ? oneLine : oneLine.substring(noNewline + 1);
Serial.println(onlyLine);
char names[] = "Alice,Bob,Charlie,David";
for (char *token = std::strtok(names, ","); token != nullptr;
token = std::strtok(nullptr, ",")) {
Serial.println(token);
}
String fields = "red,green,blue";
unsigned int start = 0;
while (true) {
int nextSeparator = fields.indexOf(',', start);
if (nextSeparator == -1) {
Serial.println(fields.substring(start));
break;
}
Serial.println(fields.substring(start, nextSeparator));
start = nextSeparator + 1;
}
}
void loop() {}
#ifndef ARDUINO
int main() {
setup();
return 0;
}
#endif
Hello
sensor1
delimiter not found
People
single line
Alice
Bob
Charlie
David
red
green
blue
主機端相容層只能透過一般 C++ 編譯器檢查程式邏輯,無法模擬實體開發板或 Arduino 硬體。選擇 Arduino 環境時,條件式程式碼會使用核心實際提供的 String 和 Serial 實作。程式會在擷取前檢查 indexOf(),並在 lastIndexOf('\n') == -1 時改用完整的單行字串。程式碼與輸出範例中的標籤(例如 delimiter not found)維持英文;範例結果包括 Hello、sensor1、People、single line、四個姓名,以及 red、green 和 blue。
使用 indexOf() 尋找分隔符並擷取欄位
分隔符位置會變動時,請呼叫 indexOf(),並將它回傳的位置作為 substring() 的結束索引。找不到指定字元或文字時,Arduino 會回傳 -1。將結果傳給 substring() 之前,務必先檢查這個值;否則它不能作為有效的欄位邊界。
例如,若有 String record = "sensor1:value1",可以搜尋 ':'。結果為非負數時,record.substring(0, separator) 會擷取出 sensor1。如果找不到冒號,請明確決定要將整筆記錄視為一個欄位,還是拒絕這筆資料。若要擷取多個欄位,請從前一個分隔符後的字元位置繼續搜尋。
從多行字串擷取最後一行
使用 lastIndexOf('\n') 找出最後一個換行字元,接著從它後一個字元開始擷取。若從換行字元本身開始,結果會包含換行符,序列輸出看起來可能因此多出一個空行。如果字串中沒有換行符,lastIndexOf() 會回傳 -1;此時應另外處理,例如將整個字串視為唯一的一行。
若字串分別包含 hello world、Greetings 和 People 三行,最後一行的擷取起點是 lastIndexOf('\n') + 1,擷取結果為 People。
使用 strtok() 分割可修改的字元陣列
第一次呼叫 strtok() 時,必須傳入可寫入且以 null 字元結尾的字元陣列。它找到欄位時會將陣列中的分隔符替換成 null 字元,因此不可傳入字串常值,也不可預期原始陣列會保持不變。若要繼續掃描同一個陣列,請呼叫 strtok(nullptr, delimiters);回傳 nullptr 時即停止。
分隔符引數代表一組個別字元,而不是多字元的分隔字串。例如,":|" 會在每個冒號或直線符號處分割。連續的分隔符會被略過,因此 strtok() 不會保留空欄位。若需要保留空欄位或原始輸入,請改用手動解析。
使用手動解析迴圈分割分隔文字
當格式需要採用分詞器未提供的規則時,手動迴圈很有用。先搜尋分隔符並處理它之前的範圍,再將下一次搜尋的起點移到分隔符之後。迴圈結束後,也要處理剩餘的字串尾端,即使輸入不是以分隔符結尾也一樣;如此才能取得範例中的最後一個欄位。
若有多種分隔符,請逐一檢查每個字元是否屬於指定集合,並明確定義相鄰分隔符是否代表空欄位。若要保留空欄位,遇到相鄰分隔符時,應處理兩者之間長度為零的範圍,而不是直接跳過。若要保留來源資料,可以直接讀取原始 Arduino String 而不修改它,或先複製字元陣列,再使用會修改資料的分詞器。
選擇合適的方法並處理邊界情況
索引固定時使用 substring();必須先尋找分隔符時使用 indexOf();需要尋找最後一次出現的位置時使用 lastIndexOf()。如果資料是簡單的欄位串流,而且可以接受修改字元陣列並捨棄空欄位,則可使用 strtok()。若格式對多種分隔符、空欄位或保留輸入資料有明確規則,手動迴圈通常更合適。
擷取欄位前,先決定找不到分隔符時應如何處理,並檢查是否回傳 -1。也請記住,Arduino 的 substring() 結束索引不包含在結果中;從換行符後擷取文字時,通常也應排除換行符。這些選擇會決定結果是否只包含預期的字元。