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() 結束索引不包含在結果中;從換行符後擷取文字時,通常也應排除換行符。這些選擇會決定結果是否只包含預期的字元。