Resource icon

[C] Split Funktion - String an Trennzeichen in einen Vector von Teilstrings zerlegen

Programmiersprache(n)
C (ANSI, abwärtskompatibel)
Betriebssystem(e)
unspezifisch
Die strtok() Funktion verändert den Originalstring und es gibt keine Möglichkeit aufeinanderfolgende Trennzeichen als Einzeltrennzeichen zu behandeln. Die folgende SplitAlloc() Funktion hält Einstellungen bereit, die das erlauben. Die einzelnen Tokens werden in einen allozierten Vector kopiert. Zur Freigabe wird die SplitFree() Funktion mitgeliefert.
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/** \brief  String an Delimiters (Trennzeichen) in Tokens (Teilstrings) zerlegen
*
*  \param  str             String, der gesplittet werden soll
*  \param  delims          String mit den Delimitern
*  \param  mergeDelims     Indikator, wie aufeinanderfolgende Delimiters behandelt werden sollen [0, 1 oder 2]:
*                          - Wenn 0, werden aufeinanderfolgende Delimiters separat behandelt, sodass
*                            in diesem Fall Tokens mit einer Länge von 0 erzeugt werden.
*                          - Wenn 1 oder 2, werden Delimiters am Stringanfang und Stringende ignoriert und
*                            aufeinanderfolgende Delimiters im String wie ein einzelnes behandelt.
*                          - Wenn 0 oder 1, werden die Zeichen in delims als Einzelzeichen gewertet.
*                          - Wenn 2, wird delims als String gewertet (alle Zeichen in der angegebenen Reihenfolge müssen gefunden werden).
*  \param  maxTok          Maximale Anzahl an Tokens.
*                          - Der Stringanfang wird solange an den Delimitern in Tokens gesplittet, bis
*                            die angegebene Anzahl Tokens erreicht wird. Das letzte Token enthält somit
*                            möglicherweise noch weitere Delimiters.
*                          - Sollen alle Delimiters behandelt werden, so ist -1 zu übergeben.
*  \param  pTokens         Pointer auf einen Vektor der Einzeltokens. Der Vektor muss mit der SplitFree
*                          Funktion wieder freigegeben werden.
*  \param  pTokCount       Pointer auf die Anzahl der Tokens im Vektor.
*
*  \return int             1 bei Fehler, sonst 0
*/
int SplitAlloc(const char *const str, const char *const delims, const int mergeDelims, const size_t maxTok, char ***const pTokens, size_t *const pTokCount)
{
  size_t len = 0; // Länge des Stings incl. Nullterminierung.
  char *pChar = NULL, // Zeiger auf ein Ttrennzeichen.
       *pOrigin = NULL, // Ursprünglich allozierter Pointer für die Tokens.
       **ppChar = NULL; // Erweiterung des Tokenvektors.

  if (pTokens)
    *pTokens = NULL; // Wert im Fehlerfall.
  if (pTokCount)
    *pTokCount = 0; // Wert im Fehlerfall.

  if (!str // Plausibilitätsprüfungen ...
      || !delims
      || mergeDelims < 0
      || mergeDelims > 2
      || !maxTok
      || !pTokens
      || !pTokCount
      || !(*pTokens = (char**)malloc(2 * sizeof(char*)))) // ... und Speicherreservierung für den Pointer auf das erste Token und den ursprünglich allozierten Pointer für die Tokens.
    return 1; // Bei Fehler, raus hier.

  if (!(**pTokens = pOrigin = (char*)malloc((len = strlen(str) + 1)))) // Reservierung des Speicherbereichs für die Tokens. Bei Fehlschlag ...
  {
    free(*pTokens); // ... Speicherbereich für den Tokens-Vektor freigeben, ...
    *pTokens = NULL; // ... Vector NULL setzen, ...
    return 1; // ... raus hier.
  }

  pChar = strncpy(**pTokens, str, len); // String kopieren.
  *pTokCount = 1; // Anzahl Tokens ist 1.
  (*pTokens)[1] = pOrigin; // Ursprünglich allozierten Pointer zuweisen.

  char *(__cdecl *search_fn)(const char *, const char *) = mergeDelims == 2 ? strstr : strpbrk; // je nach Art der Delimiterbehandlung, ist die Suchfunktion entweder strstr oder strpbrk.
  size_t delim_len = mergeDelims == 2 ? strlen(delims) : 1u; // je nach Art der Delimiterbehandlung, ist die Länge des Delimiters entweder die der gesamten Zeichenfoge oder nur ein Zeichen.

  while (*pTokCount < maxTok && ((pChar = search_fn(pChar, delims)))) // Solange die maximale Anzahl an Tokens nicht erreicht und ein Delimiter gefunden wurde ...
  {
    *pChar = 0; // ... Delimiter durch Nullterminierung ersetzen, ...

    char *next = pChar + delim_len;
    if (mergeDelims && (*pTokens)[*pTokCount - 1] == pChar) // ... wenn aufeinanderfolgende Delimiters wie ein einzelnes Delimiter behandelt werden sollen und ein Delimiter das erste Zeichen im Reststring war, ...
      (*pTokens)[*pTokCount - 1] = pChar = next; // ... Pointer des aktuellen Tokens um die Delimiterlänge nach rechts schieben, ...
    else if (mergeDelims && (*next == 0 || ((mergeDelims == 1 && strchr(delims, *next) != NULL) || (mergeDelims == 2 && strncmp(next, delims, delim_len) == 0)))) // ... ansonsten, wenn aufeinanderfolgende Delimiters wie ein einzelnes Delimiter behandelt werden sollen und Delimiters am Stringende gefunden werden, ...
      pChar = next; // ... Pointer schieben.
    else // ... ansonsten ...
    {
      if (!(ppChar = (char**)realloc(*pTokens, (*pTokCount + 2) * sizeof(char*)))) // ... Speichererweiterung für einen weiteren Pointer auf ein Token, bei Fehlschlag ...
      {
        free(pOrigin); // ... Speicherbereichs für die Tokens freigeben, ...
        free(*pTokens); // ... Speicherbereich für Pointer auf die Tokens freigeben, ...
        *pTokens = NULL; // ... Vector NULL setzen, ...
        *pTokCount = 0; // ... Anzahl Tokens 0 ...
        return 1; // ... und raus hier, ansonsten ...
      }

      *pTokens = ppChar; // ... Speichererweiterung für den Tokens-Vektor zuweisen ...
      (*pTokens)[(*pTokCount)++] = pChar = next; // ... Pointer auf das Token zuweisen, ...
      (*pTokens)[*pTokCount] = pOrigin; // ... ursprünglich allozierten Pointer zuweisen.
    }
  }

  return 0; // OK und raus.
}



/** \brief  Freigabe des durch die SplitAlloc Funktion allozierten Speichers.
*
*  \param  pTokens   Pointer auf den Vektor der Einzeltokens.
*  \param  tokCount  Anzahl der Tokens im Vektor.
*
*  \return void
*/
void SplitFree(char ***const pTokens, const size_t tokCount)
{
  if (*pTokens) // Plausibilitätsprüfung.
  {
    if (**pTokens && tokCount) // Plausibilitätsprüfung.
      free((*pTokens)[tokCount]); // Speicherbereichs der Tokens freigeben.

    free(*pTokens); // Speicherbereich der Pointer auf die Tokens freigeben.
    *pTokens = NULL; // Vektor auf NULL setzen, um "Dangling Pointer" zu vermeiden.
  }
}



int main(void)
{
  const char str[] = ", , Hello, , World !, , ", // String, der in einzelne Tokens aufgesplittet werden soll
             delims[] = ", "; // Trennzeichen sind Komma und Leerzeichen

  char **tokens = NULL; // Vektor für die Tokens
  size_t tokenscount = 0, // Anzahl der Tokens
         i = 0; // Schleifenindex

  puts("~~~~~~~~~~~~~~~~~~~");

  if (SplitAlloc(
        str, // String, der in einzelne Tokens aufgesplittet werden soll
        delims, // Trennzeichen
        1, // Voran- und nachgestellte Delimiters sollen ignoriert und aufeinanderfolgende Delimiters wie ein einzelner behandelt werden
        -1, // die Anzahl Tokens wird nicht limitiert, alle gefundenen Delimiters sollen behandelt werden
        &tokens, // Adresse des Vektors für die Tokens
        &tokenscount // Adresse für die Anzahl der Tokens
      ) == 0) // Wenn der Rückgabewert 0 war ...
  {
    for (i = 0; i < tokenscount; ++i) // Tokens ausgeben
      printf("\"%s\"\n", tokens[i]);

    SplitFree(&tokens, tokenscount); // Speicher freigeben
  }
  else // ... ansonsten ...
    puts("Fehler!"); // Fehlermeldung

  puts("~~~~~~~~~~~~~~~~~~~");

  if (SplitAlloc(
        str, // String, der in einzelne Tokens aufgesplittet werden soll
        delims, // Trennzeichen
        2, // Voran- und nachgestellte Delimiters sollen ignoriert und die exakte Zeichenfolge in delims als Delimiter behandelt werden
        -1, // die Anzahl Tokens wird nicht limitiert, alle gefundenen Delimiters sollen behandelt werden
        &tokens, // Adresse des Vektors für die Tokens
        &tokenscount // Adresse für die Anzahl der Tokens
      ) == 0) // Wenn der Rückgabewert 0 war ...
  {
    for (i = 0; i < tokenscount; ++i) // Tokens ausgeben
      printf("\"%s\"\n", tokens[i]);

    SplitFree(&tokens, tokenscount); // Speicher freigeben
  }
  else // ... ansonsten ...
    puts("Fehler!"); // Fehlermeldung

  puts("~~~~~~~~~~~~~~~~~~~");

  if (SplitAlloc(
        str, // String, der in einzelne Tokens aufgesplittet werden soll
        delims, // Trennzeichen
        0, // aufeinanderfolgende Delimiters sollen getrennt behandelt werden
        6, // maximal 6 Tokens sollen zurückgegeben werden
        &tokens, // Adresse des Vektors für die Tokens
        &tokenscount // Adresse für die Anzahl der Tokens
      ) == 0) // Wenn der Rückgabewert 0 war ...
  {
    for (i = 0; i < tokenscount; ++i) // Tokens ausgeben
      printf("\"%s\"\n", tokens[i]);

    SplitFree(&tokens, tokenscount); // Speicher freigeben
  }
  else // ... ansonsten ...
    puts("Fehler!"); // Fehlermeldung

  puts("~~~~~~~~~~~~~~~~~~~");

  return 0;
}
Ausgabe:
Code:
~~~~~~~~~~~~~~~~~~~
"Hello"
"World"
"!"
~~~~~~~~~~~~~~~~~~~
"Hello"
"World !"
~~~~~~~~~~~~~~~~~~~
""
""
""
""
"Hello"
" , World !, , "
~~~~~~~~~~~~~~~~~~~
Ein wenig Visualisierung zur Speicherverwaltung:
Split.png
Autor
German
First release
Last update
Bewertung
0,00 Stern(e) 0 Bewertungen

More resources from German

Latest updates

  1. Funktionserweiterung

    Delimiter kann nun auch eine komplette Zeichenfolge sein.
Oben