char text[] = "Hallo Welt"; Im Speicher sieht das so aus: |H|a|l|l|o| |W|e|l|t|0| |0|1|2|3|4|5|6|7|8|9|10|Nanu, da ist doch plötzlich noch eine 0 (nicht das Zeichen '0' sondern die Zahl 0, als Zeichen auch '\0' geschrieben). Diese 0 ist der Terminator, als das Zeichen, dass hier unsere Zeichenkette zu Ende ist. Das ist ganz wichtig, da sich alle Funktionen, die mit Arrays von Typ Char arbeiten, darauf verlassen, dass diese 0 da ist. Wir brauchen also für die 10 Zeichen ein Array mit der Länge 11 (Wir fangen beim Zählen der Position immer mit 0 an!)
char text[14] = "Hallo Welt"; // hätten wir [3] geschrieben, hätte der Compiler gemeckert Im Speicher sieht das so aus: |H|a|l|l|o| |W|e|l|t|0| x | x | x | |0|1|2|3|4|5|6|7|8|9|10|11|12 |13 |Was sind die char, die ich mit x gekennzeichnet habe? Ihr Inhalt ist uns egal, da unser letztes interessantes Zeichen die 0 ist. Wenn text eine globale Variable ist, werden sie beim Start des Arduino mit 0 initialisiert.
name = strcat(name,"!!");Anstelle "!!" könnt ihr auch eine andere Variable vom Typ Array aus char nehmen. Nehmen wir mal 2 Variablen mit Namen quelle und ziel und schauen mal, was wir damit anfangen können. Immer dran denken: Das Ziel muss genug Platz haben. Ich lasse ein paar Serial.println weg. Die könnt ihr bei Bedarf ergänzen;
char ziel[128]; char zielkurz[4]; char quelle[] = "Arduino Uno"; char vergleich[] = "Arduino Mega" int zahl; char * zeiger; // man kann mit Zeigern auch rechnen Serial.println(quelle+3); // ergibt "uino Uno" // Länge zahl = strlen(quelle); // liefert 11 also ohne die abschließende 0! // Zeiger auf die erste Position des Zeichens zeiger = strchr(quelle,'o'); Serial.println(zeiger); // "o Uno" also einen Zeiger auf das erste 'o' // man kann mit Zeigern auch rechnen zahl = zeiger - quelle; // ergibt 6, also den Index des ersten 'o' (von 0 an zählen) // Zeiger auf die letzte Position des Zeichens zeiger = strrchr(quelle,'o'); Serial.println(zeiger); // "o" also einen Zeiger auf das letzte 'o' // man kann mit Zeigern auch rechnen zahl = zeiger - quelle; // ergibt 10, also den Index des letzten 'o' (von 0 an zählen) // Kopieren in den Zielstring zeiger = strcpy(ziel, quelle); Serial.println(zeiger); // oder auch (wenn man das Ziel nicht nochmal abspeichern will strcpy(ziel, quelle); Serial.println(ziel); //Wenn der RAM langsam voll wird oder schon von Anfang an kann man konstante Zeichenketten auch in den Programmspeicher auslagern und damit RAM sparen. Am einfachsten geht das bei allem, was mit print zusammen hängt. Das ist das F()-Makro.auchnicht mehr zulässig ziel = strcpy(ziel, quelle); // mit Längenbegrenzung // ACHTUNG! strncpy hat ein Problem, wenn als Länge genau die Länge des Zielarrays abgegeben wird. Es schreibt dann keine abschließende 0; // Danke Serenifly für den Hinweis. // Es bleiben 2 Möglichkeiten das zu lösen // Man setzt die abschließende 0 selbst strncpy(zielkurz,quelle,4); zielkurz[3]='\0'; // oder man verwendet strlcpy, das vom Arduino-Compiler unterstützt wird, aber nicht im ISO-Standard enthalten ist. strlcpy(zielkurz,quelle,4); // Vergleiche ergeben die Differnz des Zeichencodes des ersten ungleichen Zeichens // aber eigentlich interessiert nur < 0, > 0 oder gleich 0 zahl = strcmp(quelle,vergleich); // ergibt 8 -> größer 0 also ist quelle größer zahl = strcmp(vergleich,quelle); // ergibt -8 -> kleiner 0 also ist quelle kleiner // bauen wir uns gleiche Zeichenketten strcpy(ziel,quelle); zahl = strcmp(ziel, quelle); // ergibt 0 weil beide gleich sind // machen wir den falschen Vergleich - der vergleicht ob der Wert der Zeiger gleich ist if (ziel == quelle) Serial.println("Zeiger gleich"); else Serial.println("zeiger ungleich"); // Der Vergleich geht auch mit Begrenzung der Zeichenzahl zahl = strncmp(quelle,vergleich,7); / ergibt 0 weil beide in den ersten 7 Zeichen mit "Arduino" anfangen // Wir können auch mit den Zeigern rechnen. Nehmen wir an, wir haben ein Array char befehl[] = "b145"; // das kann auch aus einer Einleseroutine gefüllt sein. // Das soll bedeuten die Blaue LED soll per PWM auf den Wert 145 gesetzt werden. // Als Variablen haben wir char farbe = befehl[0]; also das erste Zeichen ist unsere Farbe. // Dann interessiert uns das erste Zeichen nicht mehr, wir wollen den Rest in eine Ganzzahl umwandeln. int wert = atoi(befehl+1); // Wir zählen einfach auf den Anfang von befehl 1 dazu, dann zeigt dieser Wert auf die 1 von 145. Passt.
Serial.println("Das ist ein Text"); // hier steht der Text im RAM Serial.println(F("Das ist ein Text")); // hier steht der Text Programmspeicher, also in FlashBei der Arbeit mit Datenbanken müssen oft längere Select-Statements aufgebaut werden, die dann verschieden gefüllt werden. Wir haben einen Puffer, in dem wir unser Statement zusammenbauen. Der muss natürlich im RAM iegen, den wollen wir ja verändern.
char stmt[80]; char personalId[] = "U12345678"; // Wir wollen zwei mögliche SELECT-Statements absetzen: const PROGMEM char query1[] = "SELECT (name, vorname, gehalt, personal_id) FROM mitarbeiter WHERE personal_id = '"; const PROGMEM char query2[] = "UPDATE mitarbeiter SET GEHALT = GEHALT * 1.1 WHERE personal_id = '"; strcpy_p(stmt,query1); // unsere Grundquery strcat(stmt,personalId); // die PersonalId ranhängen strcat(stmt,"'"); // das abschließende ' für die Zeichenkette // Ergibt in stmt: SELECT (name, vorname, gehalt, personal_id) FROM mitarbeiter WHERE personal_id = 'U12345678'Genau so kann man mit der 2. Query verfahren. Man braucht im Arbeitsspeicher (RAM) nur einmal den Platz. Für die Nutzung des PROGMEM gibt es noch mehr angepasste Zeichenkettenfunktionen, Bei Interesse nach PROGMEM suchen. Es gibt noch zwei weitere Funktionen, die man als die Könige der Verarbeitung der Arrays vom Typ Char bezeichnen kann. Sowas kann man mit Strings nicht in der Eleganz und Einfachheit machen.
char eingabe[] = "22:33:11:44:-10:55"; char *ptr; // Erster Versuch ptr = strtok(eingabe, ":"); // solange was gefunden wurde while(ptr != NULL) { Serial.println(ptr); // gibt die einzelnen Zahlen aus // neu suchen NULL als Eingabe, die Funktion weiß, wo sie ist ptr = strtok(NULL,":"); }strtok hat einen kleinen Nachteil, es ist nicht wiedereintrittsfähig, dh. ich kann in dem strtok-Durchlauf nicht einen zweiten strtok-Aufrug starten. Abhilfe schafft hier strtok_r:
// Farbe:faden von:nach oder farbe:auf Wert char eingabe[] = "rot:44:180|blau:123|gruen:128:50" char *ptr, *savePtr, *p, *saveP; const char delim[] = "|"; ptr = strtok_r(eingabe,"|",&savePtr); while(ptr != NULL) { // hier haben wir alles zwischen | (außen) Serial.println(ptr); p = strtok_r(ptr,":",&saveP); while(p != NULL) { // alles zwischen : (innen) Serial.print("____");Serial.println(p); p = strtok_r(NULL, ":", &saveP); } // außen neu testen ptr = strtok_r(NULL,delim,&savePtr); }Hier mal noch ein Beispiel, wie man Uhrzeit und Datum einfach formatiert an eine Zeichenkette anhängen kann. Das Schöne dabei ist, die Variable "buf" in den Funktionen benötigen nur so lange Speicher, wie die Funktion läuft.
int stunde = 3, minute = 29, sekunde = 7, tag = 7, monat = 8, jahr = 2016; char puffer[20]; // Hängt die formatierte Uhrzeit an eine Zeichenkette an void addZeit(char * ziel, int st, int mi, int se) { char buf[9]; // 00:00:00 buf[0] = (st / 10) + '0'; // Zehner buf[1] = (st % 10) + '0'; // Einer buf[2] = ':'; buf[3] = (mi / 10) + '0'; buf[4] = (mi % 10) + '0'; buf[5] = ':'; buf[6] = (se / 10) + '0'; buf[7] = (se % 10) + '0'; buf[8] = 0; // die abschließende 0 strcat(ziel,buf); } // Hängt das formatierte Datum an eine Zeichenkette an void addDatum(char * ziel, int ta, int mo, int ja) { char buf[11]; // 00.00.0000 buf[0] = (ta / 10) + '0'; // Zehner buf[1] = (ta % 10) + '0'; // Einer buf[2] = '.'; buf[3] = (mo / 10) + '0'; buf[4] = (mo % 10) + '0'; buf[5] = '.'; buf[6] = (ja / 1000) + '0'; // Tausender ja = ja % 1000; // Rest 3-stellig buf[7] = (ja / 100) + '0'; // Hunderter ja = ja % 100; // Rest 2-stellig buf[8] = (ja / 10) + '0'; // Zehner buf[9] = (ja % 10) + '0'; // Einer buf[10] = 0; // die abschließende 0 strcat(ziel,buf); } void setup() { Serial.begin(115200); Serial.println("start"); strcpy(puffer,"Zeit: "); addZeit(puffer,stunde,minute,sekunde); Serial.println(puffer); strcpy(puffer,"Datum: "); addDatum(puffer, tag,monat,jahr); Serial.println(puffer); } void loop() { }Um formatiert Zahlen auszugeben, kann man auch die Funktionen der printf-Familie Nutzen. Achtung! Auf den 8-Bit-AVR ist die Unterstützung für Fließkommazahlen nicht eingebaut.
printf("Format", Liste, von, Variablen); // Ausgabe auf der Konsole, z.B. Serial - nicht bei AVR! sprintf(puffer, "Format", Liste, von, Variablen); // Ausgabe in puffer snprintf(puffer, laenge, "Format", Liste, von, Variablen); // Ausgabe in puffer mit max. laengeDabei gibt Format an, wie es ausgegeben werden soll (Infos hier) und dahinter kommt eine mit Komma getrennte Liste der Variablen, die ausgegeben werden sollen.
char puffer[9]; byte std = 12, min = 3, sec = 0, anz; anz = snprintf(puffer, sizeof(puffer), "Zeit: %02d:%02d:%02d", std, min, sec); // ergibt "Zeit: 12:03:00"In anz steht dann die tatsächlich ausgegebene Anzahl Zeichen.
Es gibt auf Arduino auch noch die Varianten mit _P hinten am Funktionsnamen (z.B. snprintf_P), bei denen der Formatstring im Flash gehalten wird. Dabei gibt es noch eine Besonderheit auf AVR: Zeichenketten können mit %S auch vom Flash eingebaut werden. Bei folgendem Beispiel liegen beide Zeichenketten im Flash.
const char header_1[] PROGMEM = "Text im Flash"; sprintf_P(buffer, PSTR("The column header is %S"), header_1);Immer wieder gebraucht wird eine Funktion zum Einlesen von einer seriellen Schnittstelle bis zum Zeilenvorschub (NL oder '\n'). Dabei werden Steuerzeichen ausgeblendet und am Ende der Puffer mit '\0' abgeschlossen, si dass alle Zeichenkettenfunktionen damit arbeiten können.
// Länge Nutzdaten + 1 const uint8_t BUFLEN = 11; char puffer[BUFLEN]; // liest bis zum Zeilenende oder bis der Puffer voll ist und gibt dann true zurück boolean readSerialNL() { static uint8_t idx = 0; static boolean fertig = false; char c; // Neubeginn initialisieren if (fertig && idx > 0) { idx = 0; fertig = false; } if (Serial.available()) { // hier könnte man evtl. auch while nehmen c = Serial.read(); if (c == '\n' || idx == BUFLEN -1) { // Zeilenvorschub oder voll puffer[idx] = '\0'; fertig = true; } else if (c >= 0x20 && idx < BUFLEN -1) { // keine Steuerzeichen puffer[idx++] = c; } } return fertig; } void setup() { Serial.begin(115200); Serial.println("Start"); } void loop() { // solange lesen lassen, bis Zeilenende erkannt wurde boolean anzeigen = readSerialNL(); if (anzeigen) Serial.println(puffer); }
String s1 = "Das ist ein Text "; int i1 = 15, i2 = 33; s1 = s1 +String(i1) + ":" + String(i2); Serial.println(s1); // ergibt: Das ist ein Text 15:33Was passiert aber (vereinfacht) dabei. Der String s1 existiert. Aus i1 wird ein neuer String erstellt und gemeinsam mit dem alten s1 in einen neuen s1 kopiert. Das gleiche passiert beim ":" und bei i2.
// Bibliothek einbinden #include <PString.h> // unser Speicher char puffer[30]; // Wir legen einen PString an PString str(puffer, sizeof(puffer)); // Zuweisung einer Zeichenkette str = "Hallo"; // was passt insgesammt rein (30) inclusive '\0' Serial.println(str.capacity()); // Wieviel ist belegt? (5) Serial.println(str.length()); // Wir hängen eine Zeichenkette an str += " Arduino"; // Hallo Arduino Serial.println(str); Serial.println(puffer); // Neuzuweisung str = "Test"; Serial.println(puffer); // leer machen str.begin(); // Wir können alle Vorteile von Print nutzen, z.B. HEX-Darstellung str.print("Das ist eine Zahl "); str.print(123,HEX); Serial.println(str); // Als Abschluss: Wir können PStrings inhaltlich mit == vergleichen str = "Hallo"; str += " Arduino"; if (str == "Hallo Arduino") Serial.println("beide sind gleich"); else Serial.println("beide sind ungleich");Ich hoffe, diese kurze Zusammenfassung erleichtert etwas den Einstieg in die Welt der Zeichenketten in C.