Strings vergleichen. Geht das einfacher?

Ich werte MQTT Messages aus und muss dazu Topics, die ankommen mit hinterlegten Topics vergleichen.
Da ich aber mehrere Sketches ähnlicher Art habe halte ich das mit den Bezeichnungen so allgemein wie möglich.
Ich definiere daher z.B. wie folgt:

#define HOSTNAME "esp8266_1"
#define MYTOPIC1 "mytopic1"
#define MYTOPIC2 "mytopic2"

Per MQTT kommt dann "esp8266_1/mytopic1"

Ich möchte dann erkennen können, ob nun mytopic1 oder mytopic2 angekommen ist.

Man könnte dass ja so machen, indem man einfach mit "esp8266_1/mytopic1" vergleicht,
also strcmp (mqtttopic, "esp8266_1/mytopic1").

Ich möchte aber gerne mit MYTOPIC1 vergleichen, da ich die selben topics unter Umständen auch per serieller Schnittstelle an ein Arduino weiter sende und da den HOSTNAME nicht mitschicken möchte.

Aktuell gehe ich daher wie folgt vor:

    strcpy (l_str, HOSTNAME);
    strcat (l_str, "/");
    strcat (l_str, MYTOPIC1 );
    if (strcmp (mqtttopic, l_str) == 0)

Um mir den mständlichen Mist mit dem Zusammenstringen von HOSTNAME "/" und MYTOPIC1 zu sparen, suche ich nun eine Möglichkeit, mit der ich einfach prüfen kann, ob ein Teilstring in einem String enthalten ist.
Ich verwende char arrays und nicht etwa die Klasse String.

Kurz gefragt: wie kann ich am einfachsten erkennen, ob MYTOPIC1 in mqtttopic enthalten ist?

Oder »strstr()« – einen String nach dem Auftreten eines Teilstrings durchsuchen

noiasca:
strpos?

Das habe ich in der Referenz nicht gefunden.

Ich habe aber inzwischen strstr gefunden.

Habe mir damit nun eine function gebaut, die wie folgt aussieht:

int strcompare (char *s1 , char *s2) {
  char *found;
  found = strstr(s1, s2);
  if (found == NULL)
    return 1;
  else
    return 0;
}

Ein erster Test hat funktioniert. Fällt da vielleicht irgendjemanden was auf, was man nicht so machen sollte? Bin noch nicht so der Hardcoreexperte in Arduinoprogrammierung.

agmue:
Oder »strstr()« – einen String nach dem Auftreten eines Teilstrings durchsuchen

Danke. Das hat sich mit meinem letzten Beitrag überschnitten. Ja, strstr habe ich inzwischen auch gefunden und das scheint zu funktionieren. Ich muss nur sicherstellen, dass die Namen der topics so gewählt sind, dass es keine 2 gibt, wovon eines als Teil in einem anderen enthalten ist, wie z.B.
"Mytopic1" und "topic1"

Das kann ich aber sicherstellen.

Wäre erstmal interessant, welchen PubSubClienten Du verwendest, beziehungsweise ob Strings oder char-Arrays ankommen (sieht mir auf den ersten Blick danach aus)

In Deiner Funktion würde ich vielleicht den Datentyp auf bool ändern, Du gibts ja nur 1 oder 0 zurück.

Des Weiteren empfehle ich, const char* statt #define zu verwenden (der Typsicherheit wegen)

DerLehmi:
Wäre erstmal interessant, welchen PubSubClienten Du verwendest, beziehungsweise ob Strings oder char-Arrays ankommen (sieht mir auf den ersten Blick danach aus)

Ich verwende #include <PubSubClient.h>
und der Header der Funktion sieht so aus:

void callback(char* topic, byte* payload, unsigned int length) {

Habe ich aus einem Beispiel genommen und dann immer so beibehalten.

In Deiner Funktion würde ich vielleicht den Datentyp auf bool ändern, Du gibts ja nur 1 oder 0 zurück.

Ich habe irgendwann mal ein Beispiel für Stringvergleich gefunden wo strcmp auf == 0 geprüft wurde.
Daher habe ich das jetzt so gemacht. Habe ganz ignoriert, dass boolean in C ja auch 0 und 1 bedeuten. Komme von einer Programmiersprache wo man da in true und false denkt.
Werde das also auf boolean ändern. Macht Sinn.

Des Weiteren empfehle ich, const char* statt #define zu verwenden (der Typsicherheit wegen)

Komischerweise mixe ich das in meinen Sketches. Einige Konstanten habe ich über #define definiert und andere wiederum über const char*.
Bei näherer Betrachtung sehe ich das erst jetzt und finde es in der Tat auch ziemlich inkonsequent. Kommt davon, wenn man sich aus unterschiedlichen Beispielen Codesnippets zusammen trägt.
Ist es also besser const char* zu verwenden?
Dann werde ich das in allen meinen Sketches ändern.

helste:
Ich habe irgendwann mal ein Beispiel für Stringvergleich gefunden wo strcmp auf == 0 geprüft wurde.

strcmp hat ja auch 3 Rückgabewerte

int strcmp ( const char * s1,
const char * s2
)

Compare two strings.

The strcmp() function compares the two strings s1 and s2.

Returns
The strcmp() function returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2. A consequence of the ordering used by strcmp() is that if s1 is an initial substring of s2, then s1 is considered to be "less than" s2.

Ah klar, kleiner, gleich und größer. In meinem Fall brauche ich das nicht und daher reicht boolean. Ich brauche ja nur gleich oder ungleich.

Fällt da vielleicht irgendjemanden was auf, was man nicht so machen sollte?

Ja, durchaus!

bool strcompare (const char *s1 , const char *s2) 
{
    return strstr(s1, s2);
}

Damit sollte klar sein, dass du da keine Wrapper Funktion drum rum brauchst.
Die ist flüssiger als Wasser. Überflüssig.
Das gilt ganz besonders für das if Konstrukt da drin.

Für die Bedenkenträger:
Ja, ein NULL Pointer wird zu false gecasted, und jeder andere zu true.
Da muss man nicht selber Hand anlegen.

Beispiel:

const char heuhaufen[] = "Zwiebelkuchen";
const char nadel[]     = "bel"; 

void setup() 
{
  Serial.begin(9600);
  Serial.println("Start");
  
  if(strstr(heuhaufen,nadel)) Serial.println("gefunden");
}

void loop() 
{

}

dass boolean in C ja auch 0 und 1 bedeuten.

Falsche Baustelle!
Wir sind hier nicht in C, sondern in einer C++ Welt.

Überflüssig.

Na ja, viele Bibliotheken sind voll von sowas.
Der Rückgabewert bool statt char* und der Name sind unterschiedlich. Das ist Grund genug für einen Wrapper. Man kann es ja inline deklarieren, wenn der Compiler das nicht schon von alleine hinkriegt.

Der Witz ist eher, dass die Standardfunktion strcmp 0 (false) bei Gleichheit zurückliefert.
Und dass strcompare nicht auf Gleichheit, sondern auf "enthält" prüft.

Ob man den impliziten Cast nutzt, oder ihn explizit schreibt, ist recht egal.
Einzig für Anfänger evtl. leichter lesbar.
Irgend einen anderen Nutzen gibt es nicht.

Ebenso:

if (found == NULL)
Besser wäre: if (found == nullptr)

if (!(bool)found)
if (!reinterpret_cast(found))

Dieses: if(!found) macht allerdings genau das gleiche.

Damit sind ca 1/2 Dutzend Möglichkeiten beieinander, welche das gleiche tun.

Immer bedenken:
Jede überflüssige Zeile, jedes überflüssige Statement ist eine potentielle (und völlig unnötige) Fehlerquelle.

Na ja, viele Bibliotheken sind voll von sowas.

Tausend Fliegen können nicht irren .....

Das ist Grund genug für einen Wrapper.

Nutze ich einen überflüssigen Wrapper, muss ich überflüssiger Weise in die Projektdoku (falls überhaupt eine existiert) schauen, was die Funktion tut.
Verzichte ich darauf, dann reicht das gelernte C++ Wissen aus dem Buch.

int strcompare (char *s1 , char *s2) {
  char *found;
  found = strstr(s1, s2);
  if (found == NULL)
    return 1;
  else
    return 0;
}

Wenn ich mich frage:
Was bringt dieser Wrapper an Mehrwert, gegenüber der nackten Anwendung von strstr()?
Dann laute meine Antwort, für mich: Keinen!
Im Gegenteil. Es würde mich stören.

Durchaus möglich, dass meine Kriterien nicht für alle gelten.
Da ist aber auch nichts schlimmes dran.
Wäre echt langweilig, wenn wir alle gleich wären.

Nutze ich einen [...] Wrapper, muss ich [...] in die Projektdoku (falls überhaupt eine existiert) schauen, was die Funktion tut.

+1 (Da gebe ich dir sehr Recht)

Verzichte ich darauf, dann reicht das gelernte C++ Wissen aus dem Buch.

Selbst da gucke ich gern zur Sicherheit mal nach, wenn auch nicht umständlich im Buch.

Aber die bösen Fehler sind ja die, wo man gar nicht auf die Idee kommt, nachzugucken. :wink:

Aber die bösen Fehler sind ja die, wo man gar nicht auf die Idee kommt, nachzugucken. :wink:

Da gebe ich dir mal zu 100% recht!

Wie oft erleben wir es hier im Forum, dass die fehlerhafte Stelle ausgeblendet wird....

Vielen Dank für die Analyse und die Tipps. Kann da nun nur voll und ganz zustimmen. Ich muss zugeben, dass ich so tief nicht in die C++ Programmierung vorgestoßen bin. Mache das nur so weit wie ich es für meine Sketches brauche und orientiere mich da an Beispielen. Ist aber gut und ich bin dankbar dafür, wenn man bessere Möglichkeiten aufgezeigt bekommt. Nochmals danke dafür. Werde jetzt meine Sketches umschreiben und das berücksichtigen.

Ich habe noch eine Frage bezüglich zusammenstringen von einzelnen strings.
Wenn ich z.B. 3 strings zusammenketten möchte, dann mache das momentan meiner Meinung nach umständlich, aber ich habe noch keinen einfacheren Weg gefunden. Hier ein kleines Beispiel, wo ich ein topic für MQTT zusammenbastle.

const char* HOSTNAME = "esp8266_1";

const char* SETLIGHT = "setlight";

#define DATA_SIZE 25

char MQTTtopic[DATA_SIZE];
char MQTTpayload[DATA_SIZE];


void sendmqtt () {
   strcpy (MQTTtopic, HOSTNAME);
   strcat (MQTTtopic, "/");
   strcat (MQTTtopic, SETLIGHT );
   strcpy (MQTTpayload, "ON");
   client_MQTT.publish(MQTTtopic, MQTTpayload);
}

Ich bin mir ziemlich sicher, dass da einiges verbesserungswürdig wäre. Kann bestimmt auch einfacher erledigt werden, oder?

Hinweis: das ist so wie es da steht, kein realer Code. Wäre ja sinnlos eine Funktion zu schreiben, die immer den gleichen fixen Wert sendet. In meinem Sketch wird MQTTtopic über die serielle Schnittstelle empfangen und soll dann zusammen mit dem Hostnamen als Präfix gesendet werden.
In einigen Fällen bastle ich das aber auch zusammen, nachdem ein Button gedrückt wird. Dann wird in der Buttendrückfunktion nur MQTTtopic und MQTTpayload versorgt und in der Sendefunktion soll dann der Hostrname und das slash dazugestringt werden bevor es dann versendet wird.
Eventuell wäre es aber auch gleich sinnvoll, topic und payload an die Funktion als Parameter zu übergeben. Dann muss ich nicht noch extra variablen deklarieren.

Ich verwende #include <PubSubClient.h>
und der Header der Funktion sieht so aus:

void callback(char* topic, byte* payload, unsigned int length) {

-->Es gibt 2 PubSubClienten:
a) original von knolleary. Dieser nutzt char-Arrays, darum auch ein begrenzter Zeichensatz.
b) abgeändert von Imroy, hierbei werden Strings (ich unterscheide zwischen String und string) genutzt. Nachteil: speicherhungrig, Vorteil: einfaches Handling (Vergleiche, Zeichenketten bilden etc)

Du nutzt den Originalen, dazu würde ich auch raten.

Wenn ich z.B. 3 strings zusammenketten möchte, dann mache das momentan meiner Meinung nach umständlich, aber ich habe noch keinen einfacheren Weg gefunden.

-->Ich hatte es damals über sprintf() gemacht, z.B.:

sprintf(buff_topic, "home/WiFiNode/NodeNr%02d/DeviceID%03d", WIFINODE, DEVID);

Ja, String hatte ich zu Beginn häufig verwendet, weil es einfach war. Damit habe ich dann aber bald aufgehört, weil es, wie Du schon schriebst, mehr Speicher benötigt, aber auch weil es sehr instabil lief. Die Sketches sind alle nach relativ kurzer Laufzeit immer wieder abgeschmiert. Seit ich nur noch char-Arrays verwende passiert das nicht mehr.

sprintf verwende ich aktuell zur Umwandlung von numerischen typen in char-Arrays. Auf die Idee damit char-Arrays zu verbinden, bin ich noch nicht gekommen.