Icon Ressource

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

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> // SplitAlloc, SplitFree
#include <string.h> // SplitAlloc
#include <stdbool.h> // SplitAlloc

/** \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 false bei Fehler, sonst true */
bool SplitAlloc(const char *const str, const char *const delims, const unsigned mergeDelims, const size_t maxTok, char ***const pTokens, size_t *const pTokCount)
{
  if (pTokens)
    *pTokens = NULL; // Wert im Fehlerfall.
  if (pTokCount)
    *pTokCount = 0U; // Wert im Fehlerfall.

  size_t delimLen = 0U,
         capacity = maxTok < (size_t)(-1) ? maxTok + 2U : (size_t)64U; // Aus Performancegründen vorab allokierte Anzahl Pointerwerte.
  if (!str || !delims || !(delimLen = strlen(delims)) || mergeDelims > 2U || !maxTok || !pTokens || !pTokCount // Plausibilitätsprüfungen ...
      || !(*pTokens = (char**)malloc(capacity * sizeof(char*)))) // ... und Speicherreservierung für die Tokens.
    return false; // Bei Fehler, raus hier.

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

  char *bufIt = strcpy(**pTokens, str); // String kopieren.
  ++(*pTokCount); // Anzahl Tokens ist 1.
  (*pTokens)[1] = buffer; // Ursprünglich allozierten Pointer zuweisen.

  delimLen = mergeDelims == 2U ? delimLen : (size_t)1u; // je nach Art der Delimiterbehandlung, ist die Länge des Delimiters entweder die der gesamten Zeichenfoge oder nur ein Zeichen.
  while (*pTokCount < maxTok && ((bufIt = (mergeDelims == 2U ? strstr : strpbrk)(bufIt, delims)))) // Solange die maximale Anzahl an Tokens nicht erreicht und ein Delimiter gefunden wurde ...
  {
    *bufIt = '\0'; // ... Delimiter durch Nullterminierung ersetzen, ...

    char *next = bufIt + delimLen;
    if (mergeDelims && (*pTokens)[*pTokCount - 1] == bufIt) // ... wenn aufeinanderfolgende Delimiters wie ein einzelnes Delimiter behandelt werden sollen und ein Delimiter das erste Zeichen im Reststring war, ...
      (*pTokens)[*pTokCount - 1] = bufIt = next; // ... Pointer des aktuellen Tokens um die Delimiterlänge nach rechts schieben, ...
    else if (mergeDelims && (*next == 0 || ((mergeDelims == 1U && strchr(delims, *next) != NULL) || (mergeDelims == 2U && strncmp(next, delims, delimLen) == 0)))) // ... ansonsten, wenn aufeinanderfolgende Delimiters wie ein einzelnes Delimiter behandelt werden sollen und Delimiters am Stringende gefunden werden, ...
      bufIt = next; // ... Pointer schieben.
    else // ... ansonsten ...
    {
      if (*pTokCount + 2U > capacity) // .. falls die Kapazität nicht mehr ausreicht ...
      {
        char **tmp = (char**)realloc(*pTokens, (capacity <<= 1) * sizeof(char*)); // ... Speichererweiterung für weitere Pointer auf ein Token ...
        if (!tmp) // ... bei Fehlschlag ...
        {
          free(buffer); // ... Speicherbereichs für die Tokens freigeben, ...
          free(*pTokens); // ... Speicherbereich für Pointer auf die Tokens freigeben, ...
          *pTokens = NULL; // ... Vector NULL setzen, ...
          *pTokCount = 0U; // ... Anzahl Tokens 0 ...
          return false; // ... und raus hier, ansonsten ...
        }

        *pTokens = tmp; // ... Speichererweiterung für den Tokens-Vektor zuweisen ...
      }

      (*pTokens)[(*pTokCount)++] = bufIt = next; // ... Pointer auf das Token zuweisen, ...
      (*pTokens)[*pTokCount] = buffer; // ... ursprünglich allozierten Pointer zuweisen.
    }
  }

  return true; // 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. */
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
      )) // Wenn erfogreich ...
  {
    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
      )) // Wenn erfogreich ...
  {
    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
      )) // Wenn erfogreich ...
  {
    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
Aufrufe
1.023
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von german

Oben Unten