C-Programmierung: Zeichenkettenfunktionen

Für die Bearbeitung von Strings stellt C eine Reihe von Bibliotheksfunktionen zur Verfügung. Um sie verwenden zu können, muss mit der Präprozessor-Anweisung #include die Headerdatei string.h eingebunden werden.

char* strcpy(char* Ziel, const char* Quelle);

Kopiert einen String in einen anderen (Quelle nach Ziel) und liefert Zeiger auf Ziel als Funktionswert. Bitte beachten Sie, dass eine Anweisung text2 = text1 für ein Array nicht möglich ist. Für eine Kopie eines Strings in einen anderen ist immer die Anweisung strcpy nötig, da eine Zeichenkette immer zeichenweise kopiert werden muss.

Beispiel:

#include <stdio.h>
#include <string.h>

int main(void)
{
  char text[20];

  strcpy(text, "Hallo!");
  printf("%s\n", text);
  strcpy(text, "Ja Du!");
  printf("%s\n", text);
  return 0;
}

Ausgabe:

Hallo!
Ja Du!
char* strncpy(char* Ziel, const char* Quelle, size_t num);

Kopiert num-Zeichen von Quelle zu Ziel. Wenn das Ende des Quelle C-String (welches ein null-Character ('\0') signalisiert) gefunden wird, bevor num-Zeichen kopiert sind, wird Ziel mit '\0'-Zeichen aufgefüllt bis die komplette Anzahl von num-Zeichen in Ziel geschrieben ist.

Wichtig: strncpy() fügt selbst keinen null-Character ('\0') an das Ende von Ziel. Soll heißen: Ziel wird nur null-terminiert wenn die Länge des C-Strings Quelle kleiner ist als num.

Beispiel:

#include <stdio.h>
#include <string.h>

int main ()
{
    char strA[] = "Hallo!";
    char strB[6];

    strncpy(strB, strA, 5);

    /* Nimm die Anzahl von Bytes in strB (6), ziehe 1 ab (= 5) um auf den letzten index zu kommen,
       dann füge dort einen null-Terminierer ein. */
    strB[sizeof(strB)-1] = '\0';

    puts(strB);

    return 0;
}

Vorsicht: Benutzen Sie sizeof() in diesem Zusammenhang nur bei Character-Arrays. sizeof() gibt die Anzahl der reservierten Bytes zurück. In diesem Fall: 6 (Größe von strB) * 1 Byte (Character) = 6.

Ausgabe:

Hallo
char* strcat(char* s1, const char* s2);

Verbindet zwei Zeichenketten miteinander. Das Stringende-Zeichen '\0' von s1 wird überschrieben. Voraussetzung ist, dass der für s1 reservierte Speicherbereich ausreichend groß zur Aufnahme von s2 ist. Andernfalls ergibt sich undefiniertes Verhalten.

Beispiel:

#include <stdio.h>
#include <string.h>
 
int main(void)
{
  char text[20];
  strcpy(text, "Hallo!");
  printf("%s\n", text);
  strcat(text, "Ja, du!");
  printf("%s\n", text);
  return 0;
}

Ausgabe:

Hallo!
Hallo!Ja, du!

Wie Sie sehen wird der String in Zeile 9 diesmal nicht überschrieben, sondern am Ende angehängt.

char* strncat(char* s1, const char* s2, size_t n);

Verbindet – so wie strcat() – zwei Zeichenketten miteinander, wobei aber nur n Elemente von s2 an s1 angehängt werden. An das Ende der Resultat-Zeichenfolge wird in jedem Fall ein '\0'-Zeichen angehängt. Für überlappende Bereiche ist das Ergebnis – soweit nicht anders angegeben – nicht definiert.

Mit dieser Funktion kann beispielsweise sichergestellt werden, dass nicht in einen undefinierten Speicherbereich geschrieben wird. Dafür wäre n so zu wählen, dass der für s1 reservierte Speicherbereich nicht überschritten wird.

Beispiel:

#include <stdio.h>
#include <string.h>

int main(void)
{
  char text[40];
  strcpy(text, "Es werden nur zehn Zeichen ");
  printf("%s\n", text);
  strncat(text, "angehaengt, der Rest nicht.", 10);
  printf("%s\n", text);
  return 0;
}

Ausgabe:

Es werden nur zehn Zeichen 
Es werden nur zehn Zeichen angehaengt
char *strtok( char *s1, const char *s2 );

Diese Funktion zerlegt einen String s1 mit Hilfe des bzw. der in s2 gegebenen Trennzeichen (token) in einzelne Teil-Strings. s2 kann also eines oder auch mehrere Trennzeichen enthalten, das heißt

char s2[] = " ,\n.";

würde beispielsweise auf eine Trennung bei Space, Komma, New-Line oder Punkt hinauslaufen.

Anmerkung: Durch strtok() wird der ursprüngliche String zerstört, dieser darf demzufolge niemals konstant (const) sein. Weiters ist die Funktion wegen der internen Verwendung von statischem Speicher nicht multithread-fähig und nicht wiedereintrittsfähig (nicht reentrant).

Beispiel:

#include <stdio.h>
#include <string.h>

int main(void) {
    char text[] = "Das ist ein Beispiel!";
    char trennzeichen[] = " ";
    char *wort;
    int i=1;
    wort = strtok(text, trennzeichen);

    while(wort != NULL) {
        printf("Token %d: %s\n", i++, wort);
        wort = strtok(NULL, trennzeichen);
    
        //Jeder Aufruf gibt das Token zurück. Das Trennzeichen wird mit '\0' überschrieben.
        //Die Schleife läuft durch bis strtok() den NULL-Zeiger zurückliefert.
    }
    return 0;
}

Ausgabe:

Token1: Das
Token2: ist
Token3: ein
Token4: Beispiel!
int strcspn(const char *string1, const char *string2);

Diese Funktion gibt die Anzahl der Zeichen am Anfang von string1 zurück, die nicht in string2 enthalten sind.

Beispiel:

#include <stdio.h>
#include <string.h>

int main(void){
    char s1[] = "Das ist ein Text";
    char s2[] = "tbc";               

    int cnt = strcspn(s1,s2);
    printf("Anzahl der Zeichen am Anfang von '%s', die nicht in '%s' vorkommen: %d\n", s1, s2, cnt);

    return 0;
}

Ausgabe:

Anzahl der Zeichen am Anfang von 'Das ist ein Text', die nicht in 'tbc' vorkommen: 6
char *strpbrk(const char *string1, const char *string2);

Gibt einen Zeiger auf das erste Zeichen in string1 zurück, das auch in string2 enthalten ist. Es wird also – wie auch bei Funktion strcspn() – nicht nach einer Zeichenkette, sondern nach einem einzelnen Zeichen aus einer Zeichenmenge gesucht. War die Suche erfolglos, wird NULL zurückgegeben.

Beispiel:

#include <stdio.h>
#include <string.h>

int main()
{
   char string1[]="Schwein gehabt!";
   char string2[]="aeiou";
   printf("%s\n", strpbrk(string1, string2));
   return 0;
}

Ausgabe:

ein gehabt!
char* strchr(char *string, int zeichen);

Diese Funktion sucht nach dem ersten Auftreten eines Zeichens zeichen in einer Zeichenkette string und gibt einen Zeiger auf dieses zurück. War die Suche erfolglos, wird NULL zurückgegeben.

Beispiel 1:

#include <stdio.h>
#include <string.h>

int main()
{
  char string[] = "Ein Teststring mit Worten";
  printf("%s\n",strchr(string, (int)'W'));
  printf("%s\n",strchr(string, (int)'T'));
  return 0;
}

Ausgabe:

Worten
Teststring mit Worten

Beispiel 2:

#include <stdio.h>
#include <string.h>

int main()
{
  char string[]="Dies ist wichtig. Dies nicht.";
  char *stelle;

  stelle=strchr(string, (int)'.');
  *(stelle+1)='\0'; /*Durch *(stelle+1) wird nicht der Punkt,
                      sondern das Leerzeichen (das Zeichen danach)
                      durch das Determinierungszeichen ersetzt*/
  printf("%s", string);

  return 0;
}

Ausgabe:

Dies ist wichtig.
char *strrchr(const char *s, int ch);

Diese Funktion sucht im Unterschied zu strchr() nicht nach dem ersten, sondern nach dem letzten Auftreten eines Zeichens ch in einer Zeichenkette s und gibt einen Zeiger auf dieses Zeichen zurück. War die Suche erfolglos, wird NULL zurückgegeben.

Beispiel 1:

Hier nutzen wir fgets(), um eine Zeichenkette von der Standard-Eingabe (stdin) einzulesen. Wenn die Eingabe nun aber weniger Zeichen umfasst als die angegebene maximale Anzahl der einzulesenden Zeichen (im Beispiel unten wären es 20), endet die resultierende Zeichenkette mit einem New-Line-Zeichen (\n). Um einen nullterminierten String zu erhalten, suchen wir daher mit einem Zeiger nach diesem und ersetzen es gegebenenfalls durch \0.

#include <stdio.h>
#include <string.h>

int main()
{
   char string[20];
   char *ptr;

   printf("Eingabe machen:\n");
   fgets(string, 20 , stdin);
   /* man setzt den zeiger auf das New-Line-Zeichen */
   ptr = strrchr(string, '\n');
   if( ptr != NULL )
   {
     /* \n-Zeichen mit \0 überschreiben */
     *ptr = '\0';
   }

   printf("%s\n",string);
   return 0;
}

Beispiel 2:

#include <stdio.h>
#include <string.h>

int main()
{
   char string[]="Dies ist wichtig. Dies ist nicht wichtig";
   char *ptr;

   // suche Trennzeichen '.' vom Ende der Zeichenkette aus
   ptr = strrchr (string, '.');

   // wenn Trennzeichen im Text nicht vorhanden, 
   // dann ist der Pointer NULL, d.h. NULL muss abgefangen werden.
   if (ptr != NULL) {           
       *ptr = '\0';                   
   }

   printf ("%s\n", string);
   return 0;
}

Der Pointer ptr zeigt nach strrchr() genau auf die Speicherstelle des Strings, in der das erste Trennzeichen von hinten steht. Wenn man nun an diese Speicherstelle das Zeichenketteendezeichen \0 schreibt, dann ist der String für alle Stringfunktionen an dieser Stelle beendet. printf() gibt den String string nur bis zum Zeichenketteendezeichen aus.

Ausgabe:

Dies ist wichtig
int strcmp(char* s1, char* s2);

Diese Funktion vergleicht zwei Zeichenketten miteinander, wobei Zeichen für Zeichen deren jeweilige ASCII-Codes verglichen werden. Wenn die beiden Strings identisch sind, gibt die Funktion den Wert 0 zurück. Sind sie unterschiedlich, liefert die Funktion einen Rückgabewert entweder größer oder kleiner 0: Ein Rückgabewert größer / kleiner 0 bedeutet, dass der ASCII-Code des ersten ungleichen Zeichens in s1 größer / kleiner ist als der des entsprechenden Zeichens in s2.

Beispiel:

#include <stdio.h>
#include <string.h>

int main()
{
  const char string1[] = "Hello";
  const char string2[] = "World";
  const char string3[] = "Hello";

  if (strcmp(string1,string2) == 0)
  {
    printf("Die beiden Zeichenketten %s und %s sind identisch.\n",string1,string2);
  }
  else
  {
    printf("Die beiden Zeichenketten %s und %s sind unterschiedlich.\n",string1,string2);
  }

  if (strcmp(string1,string3) == 0)
  {
    printf("Die beiden Zeichenketten %s und %s sind identisch.\n",string1,string3);
  }
  else
  {
    printf("Die beiden Zeichenketten %s und %s sind unterschiedlich.\n",string1,string3);
  }
	
  return 0;
}

Ausgabe:

Die beiden Zeichenketten Hello und World sind unterschiedlich.
Die beiden Zeichenketten Hello und Hello sind identisch.
int strncmp(const char *x, const char *y, size_t n);

Diese Funktion arbeitet so wie strcmp() – mit dem einzigen Unterschied, dass nur die ersten n Zeichen der beiden Strings miteinander verglichen werden. Auch der Rückgabewert entspricht dem von strcmp().

Beispiel:

#include <stdio.h>
#include <string.h>

int main()
{
   const char x[] = "aaaa";
   const char y[] = "aabb";
   int i;

   for(i = strlen(x); i > 0; --i)
      {
         if(strncmp( x, y, i) != 0)
            printf("Die ersten %d Zeichen der beiden Strings "\
                   "sind nicht gleich\n", i);
         else
            {
               printf("Die ersten %d Zeichen der beiden Strings "\
                      "sind gleich\n", i);
               break;
            }
      }
   return 0;
}

Ausgabe:

Die ersten 4 Zeichen der beiden Strings sind nicht gleich
Die ersten 3 Zeichen der beiden Strings sind nicht gleich
Die ersten 2 Zeichen der beiden Strings sind gleich
int strspn(const char *string1, const char *string2);

Diese Funktion gibt die Anzahl der Zeichen am Anfang von string1 zurück, die in string2 enthalten sind.

Beispiel:

#include <stdio.h>
#include <string.h>

int main()
{
   const char string[] = "7501234-123";
   int cnt = strspn(string, "0123456789");

   printf("Anzahl der Ziffern am Anfang von '%s': %d\n", string, cnt);

   return 0;
}

Ausgabe:

Anzahl der Ziffern am Anfang von '7501234-123': 7
size_t strlen(const char *string1);

Diese Funktion gibt die Länge eines Strings string1 (ohne das abschließenden Nullzeichen) zurück.

Beispiel:

#include <stdio.h>
#include <string.h>

int main()
{
  char string1[] = "Das ist ein Test";
  size_t length;

  length = strlen(string1);
  printf("Der String \"%s\" hat %d Zeichen\n", string1, length);

  return 0;
}

Ausgabe:

Der String "Das ist ein Test" hat 16 Zeichen
char *strstr(const char *s1, const char *s2);

Sucht nach dem ersten Vorkommen der Zeichenfolge s2 (ohne dem abschließenden Nullzeichen) in der Zeichenfolge s1 und gibt einen Zeiger auf die gefundene Zeichenfolge (innerhalb s1) zurück. Ist die Länge der Zeichenfolge s2 0, so wird der Zeiger auf s1 geliefert; war die Suche erfolglos, wird NULL zurückgegeben.

#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "Dies ist ein simpler string";
  char *ptr;
  // setzt den Pointer ptr an die Textstelle "simpler"
  ptr = strstr (str, "simpler");
  // ersetzt den Text an der Stelle des Pointers mit "Beispiel"
  strncpy (ptr, "Beispiel", 8);
  puts (str);
  return 0;
}

Ausgabe:

Dies ist ein Beispielstring

Gefahren

Bearbeiten

Bei der Verarbeitung von Strings muss man aufpassen, nicht über das Ende eines Speicherbereiches hinauszuschreiben oder zu -lesen. Generell sind Funktionen wie strcpy() und sprintf() zu vermeiden und stattdessen strncpy() und snprintf() zu verwenden, weil dort die Größe des jeweiligen Speicherbereiches angegeben werden kann.

Beispiel:

#include <string.h>
#include <stdio.h>

int main(void)
{
  char text[20];

  strcpy(text, "Dies ist kein feiner Programmtest"); // Absturzgefahr, da Zeichenkette zu lang

  strncpy(text, "Dies ist ein feiner Programmtest", sizeof(text));
  printf("Die Laenge ist %u\n", strlen(text)); // Absturzgefahr, da Zeichenkette 'text' nicht terminiert
  
  // also vorsichtshalber mit \0 abschliessen.
  text[sizeof(text)-1] = '\0';
  printf("Die Laenge von '%s' ist %u \n", text, strlen(text));

  return 0;
}

Die beiden Zeilen 8 und 11 bringen das Programm möglicherweise zum Absturz:

  • Zeile 8: strcpy() versucht mehr Zeichen zu schreiben, als in der Variable vorhanden sind, was möglicherweise zu einem Speicherzugriffsfehler führt.
  • Zeile 11: Falls das Programm in Zeile 8 noch nicht abstürzt, geschieht das eventuell jetzt. In Zeile 10 werden genau 20 Zeichen kopiert, was prinzipiell in Ordnung ist. Weil aber der Platz nicht ausreicht, wird die abschließende \0 ausgespart, die Zeichenkette ist also nicht terminiert. Die Funktion strlen() benötigt aber genau diese \0, um die Länge zu bestimmen. Tritt dieses Zeichen nicht auf, kann es zu einem Speicherzugriffsfehler kommen.

Entfernt man die beiden Zeilen 8 und 11 ergibt sich folgende Ausgabe:

Die Laenge von 'Dies ist ein feiner' ist 19

Es ist klar, dass sich hier als Länge 19 ergibt, denn ein Zeichen wird eben für das Nullzeichen verbraucht. Man muss also immer daran denken, ein zusätzliches Byte dafür einzurechnen.

Iterieren durch eine Zeichenkette

Bearbeiten

Die folgende Funktion replace_character()ersetzt in einem String ein Zeichen durch ein anderes, ihr Rückgabewert ist die Anzahl der Ersetzungen.

#include <string.h>
#include <stdio.h>

unsigned replace_character(char* string, char from, char to)
{
  unsigned result = 0;
 
  if (!string) return 0;
 
  while (*string != '\0')
  {
    if (*string == from)
    {
      *string = to;
      result++;
    }
    string++;
  }
  return result;
}
 
int main(void)
{
  char text[50] = "Dies ist ein feiner Programmtest";
  unsigned result;
 
  result = replace_character(text, 'e', ' ');
  printf("%u Ersetzungen: %s\n", result, text);
 
  result = replace_character(text, ' ', '#');
  printf("%u Ersetzungen: %s\n", result, text);
 
  return 0;
}

Der Vergleich der einzelnen Zeichen von char *string mit char from wird mit einer Schleife bewerkstelligt. Der Zeiger string (diese Schreibweise entspricht ja &string[]) verweist anfangs auf die Adresse des ersten Zeichens – durch die Dereferenzierung (*string) erhält man also dieses Zeichen selbst. Am Ende jedes Schleifendurchlaufes wird dieser Zeiger um eins erhöht, also auf das nächste Zeichen gesetzt. Falls die beiden verglichenen Zeichen identisch sind, wird das jeweiligen Zeichen *string durch to ersetzt.

Ausgabe:

5 Ersetzungen: Di s ist  in f in r Programmt st
9 Ersetzungen: Di#s#ist##in#f#in#r#Programmt#st

Die Bibliothek ctype.h

Bearbeiten

Wie wir bereits im Kapitel Variablen und Konstanten gesehen haben, sagt der C-Standard nichts über den verwendeten Zeichensatz aus. Nehmen wir beispielsweise an, wir wollen testen, ob in der Variable c ein Buchstabe gespeichert ist. Wir verwenden dazu die Bedingung

  if ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')

Unglücklicherweise funktioniert diese Bedingung zwar mit dem ASCII-, nicht aber dem EBCDIC-Zeichensatz. Der Grund dafür ist, dass die Buchstaben beim EBCDIC-Zeichensatz nicht hintereinander stehen.

Wer eine plattformunabhängige Lösung sucht, kann deshalb auf Funktionen der Standardbibliothek zurückgreifen, deren Prototypen alle in der Headerdatei ctype.h definiert sind. Für den Test auf Buchstaben können wir beispielsweise die Funktion int isalpha(int c) benutzen. Alle Funktionen, die in der Headerdatei ctype.h deklariert sind, liefern einen Wert ungleich 0 zurück wenn die entsprechende Bedingung erfüllt ist, andernfalls liefern sie 0 zurück.

Weitere Funktionen von ctype.h sind:

  • int isalnum(int c) testet auf alphanumerisches Zeichen (a-z, A-Z, 0-9)
  • int isalpha(int c) testet auf Buchstabe (a-z, A-Z)
  • int iscntrl(int c) testet auf Steuerzeichen ('\f', '\n', '\t' ...)
  • int isdigit(int c) testet auf Dezimalziffer (0-9)
  • int isgraph(int c) testet auf druckbare Zeichen
  • int islower(int c) testet auf Kleinbuchstaben (a-z)
  • int isprint(int c) testet auf druckbare Zeichen ohne Leerzeichen
  • int ispunct(int c) testet auf druckbare Interpunktionszeichen
  • int isspace(int c) testet auf Zwischenraumzeichen (engl. whitespace) (' ','\f','\n','\r','\t','\v')
  • int isupper(int c) testet auf Großbuchstaben (A-Z)
  • int isxdigit(int c) testet auf hexadezimale Ziffern (0-9, a-f, A-F)

Zusätzlich sind noch zwei Funktionen für die Umwandlung in Groß- bzw. Kleinbuchstaben definiert:

  • int tolower(int c) wandelt Groß- in Kleinbuchstaben um
  • int toupper(int c) wandelt Klein- in Großbuchstaben um