HOWTO · Arduino

String aufteilen in Arduino

Man kann einen String mit der Funktion substring() in Arduino aufteilen.

Auf dieser Seite

Arduino bietet keine einzelne String-Methode, die ein Array mit allen Feldern zurückgibt. Einen bekannten Bereich können Sie mit substring() extrahieren, ein unbekanntes Trennzeichen mit indexOf() finden oder ein beschreibbares Zeichenarray mit der C-Funktion strtok() tokenisieren. Eine manuelle Parsing-Schleife gibt Ihnen die Kontrolle über leere Felder und mehrere Trennzeichen. Entscheiden Sie anhand der bekannten Positionen, der Änderbarkeit der Eingabe und der gewünschten Behandlung der Trennzeichen, welche Methode passt.

Die folgende vollständige Skizze verwendet die Arduino-APIs String und Serial. Ein kleiner Kompatibilitäts-Shim auf dem Host ermöglicht es, dieselbe Skizze als Standard-C++ auszuführen, wenn ARDUINO nicht definiert ist; auf einem Board stellt der Arduino-Core diese APIs bereit. Die hier gezeigten seriellen Zeilen werden von der Skizze erzeugt.

substring() zur indexbasierten Extraktion in Arduino verwenden

Verwenden Sie substring(begin, end), wenn Sie den zu extrahierenden Bereich bereits kennen. Der Anfangsindex ist eingeschlossen, der Endindex hingegen ausgeschlossen. Bei "Hello,World" ergeben die Indizes 0 bis 4 also Hello; als Endargument wird 5 übergeben. Ein Aufruf mit nur einem Argument wie substring(begin) extrahiert ab diesem Index bis zum Ende des Strings.

Führen Sie diese Skizze aus. Der Code und die serielle Beispielausgabe bleiben unverändert; insbesondere ist die Meldung „delimiter not found“ weiterhin englisch:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

#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

Der Host-Shim dient nur dazu, die Logik der Skizze mit einem allgemeinen C++-Compiler zu prüfen; er bildet weder ein physisches Board noch Arduino-Hardware nach. Wenn Arduino ausgewählt ist, verwendet der bedingte Code die tatsächlichen Implementierungen von String und Serial aus dem Core. Die Skizze prüft indexOf() vor der Extraktion und behandelt lastIndexOf('\\n') == -1, indem sie den vollständigen einzeiligen String verwendet.

Mit indexOf() ein Trennzeichen finden und ein Feld extrahieren

Wenn die Position des Trennzeichens variabel ist, rufen Sie indexOf() auf und verwenden Sie die zurückgegebene Position als Endindex für substring(). Arduino gibt -1 zurück, wenn das gesuchte Zeichen oder der gesuchte Text fehlt. Prüfen Sie diesen Wert, bevor Sie das Ergebnis an substring() übergeben; andernfalls stellt es keine gültige Feldgrenze dar.

Suchen Sie beispielsweise bei String record = "sensor1:value1" nach ':'. Ist das Ergebnis nicht negativ, extrahiert record.substring(0, separator) den Wert sensor1. Fehlt der Doppelpunkt, entscheiden Sie ausdrücklich, ob der gesamte Datensatz als ein Feld behandelt oder zurückgewiesen werden soll. Suchen Sie bei mehreren Feldern jeweils ab dem Zeichen nach dem vorherigen Trennzeichen weiter.

Die letzte Zeile aus einem mehrzeiligen String extrahieren

Verwenden Sie lastIndexOf('\\n'), um den letzten Zeilenumbruch zu finden, und beginnen Sie die Extraktion ein Zeichen dahinter. Beginnt sie direkt am Zeilenumbruch, wird dieser mit einbezogen; dadurch kann die serielle Ausgabe eine zusätzliche Leerzeile enthalten. Gibt es keinen Zeilenumbruch, gibt lastIndexOf() den Wert -1 zurück. Behandeln Sie diesen Fall gesondert, zum Beispiel indem Sie den gesamten String als einzige Zeile verwenden.

Enthält ein String hello world, Greetings und People in jeweils einer eigenen Zeile, beginnt die Extraktion der letzten Zeile bei lastIndexOf('\\n') + 1. Der resultierende Text ist People.

Ein veränderbares Zeichenarray mit strtok() tokenisieren

strtok() erwartet beim ersten Aufruf ein beschreibbares, nullterminiertes Zeichenarray. Beim Finden von Tokens ersetzt die Funktion Trennzeichen im Array durch Nullzeichen. Übergeben Sie daher kein String-Literal und gehen Sie nicht davon aus, dass das ursprüngliche Array unverändert bleibt. Rufen Sie strtok(nullptr, delimiters) auf, um dasselbe Array weiter zu durchsuchen, und beenden Sie die Schleife, wenn nullptr zurückgegeben wird.

Das Argument für Trennzeichen ist eine Menge einzelner Zeichen, kein mehrstelliges Trennzeichen. ":|" trennt beispielsweise sowohl an jedem Doppelpunkt als auch an jedem senkrechten Strich. Aufeinanderfolgende Trennzeichen werden übersprungen; strtok() erhält daher keine leeren Felder. Wenn leere Felder oder die ursprüngliche Eingabe erhalten bleiben müssen, verwenden Sie manuelles Parsing.

Text mit Trennzeichen in einer manuellen Parsing-Schleife aufteilen

Eine manuelle Schleife ist hilfreich, wenn das Format Regeln erfordert, die Tokenizer nicht bieten. Suchen Sie ein Trennzeichen, verarbeiten Sie den davorliegenden Bereich und setzen Sie die nächste Startposition hinter dieses Trennzeichen. Verarbeiten Sie nach der Schleife auch den verbleibenden Rest, selbst wenn die Eingabe nicht mit einem Trennzeichen endet. So wird im Beispiel auch das letzte Feld erfasst.

Prüfen Sie bei mehreren möglichen Trennzeichen jedes Zeichen anhand der gewählten Menge und legen Sie fest, ob aufeinanderfolgende Trennzeichen leere Felder darstellen. Um ein leeres Feld zu erhalten, verarbeiten Sie zwischen zwei aufeinanderfolgenden Trennzeichen einen Bereich mit null Zeichen, statt darüber hinwegzugehen. Soll die Quelle unverändert bleiben, lesen Sie aus dem ursprünglichen Arduino-String, ohne ihn zu verändern, oder kopieren Sie ein Zeichenarray, bevor Sie einen verändernden Tokenizer verwenden.

Eine Methode auswählen und Sonderfälle behandeln

Verwenden Sie substring() für feste Positionen, indexOf(), wenn Sie zuerst ein Trennzeichen finden müssen, und lastIndexOf(), wenn das letzte Vorkommen entscheidend ist. strtok() eignet sich für einfache Tokenfolgen, wenn das Verändern eines Zeichenarrays und das Verwerfen leerer Felder akzeptabel sind. Eine manuelle Schleife ist besser geeignet, wenn das Format feste Regeln für mehrere Trennzeichen, leere Felder oder den Erhalt der Eingabe vorgibt.

Legen Sie vor dem Extrahieren eines Feldes fest, was ein fehlendes Trennzeichen bedeutet, und prüfen Sie auf -1. Denken Sie daran, dass das Ende von substring() in Arduino exklusiv ist und der Zeilenumbruch beim Extrahieren des nachfolgenden Textes normalerweise ausgeschlossen werden sollte. Diese Entscheidungen bestimmen, ob das Ergebnis genau die beabsichtigten Zeichen enthält.