簡體   English   中英

來自strtok的String.split()

[英]String.split() from strtok

我想創建一個可變長度的指針數組來進行字符串拆分。 例如,類似:

>>> s="Hello my name is Sam".split()
['Hello', 'my', 'name', 'is', 'Sam']

我目前有一種通用的打印方法:

int main() {

    char _string[] = "Hello my name is Sam";
    char * string = _string;

    char * token;
    char * delim = " ";
    token = strtok(string, delim);
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, delim);
    }
}

如何創建一個可變長度的字符串指針數組? 我的第一個想法只是選擇一個很大的數字,但這似乎並不是最好的主意。

此外,有沒有比使用以下更好的模式:

char _string[] = "Hello my name is Sam";
char * string = _string;

我覺得自己經常這樣做,如果我以“正常”方式進行操作(即,對我來說,對於初學者來說最正常的方式):

char * string = "Hello my name is Sam";

我總是會遇到一些Bus Error 應該如何正確地做到這一點?

一種方法可能是先計算令牌的數量,然后為列表動態分配內存,然后填充列表。

在代碼中,它可能看起來像這樣:

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

int count_tokens(char *str, char *delim) {
    if(str == NULL || strlen(str) == 0)
        return 0;
    int number = 0;
    char *p = str, *prev = str;
    while((p = strpbrk(p, delim)) != NULL) {
        if(p != prev)
            number++;
        prev = ++p;
    }
    if(strlen(prev) != 0)
        number++;
    return number;
}


int main() {
    char string[] = "Hello my name is Sam";
    char *delim = " ";

    int num_tokens = count_tokens(string, delim);
    char **token_list = calloc(num_tokens, sizeof(char *));

    char *token = strtok(string, delim);
    for(int i = 0; i < num_tokens && token != NULL; i++) {
        token_list[i] = token;
        token = strtok(NULL, delim);
    }

    for(int i = 0; i < num_tokens; i++) {
        printf("%s\n", token_list[i]);
    }

    free(token_list);
}

另一種更靈活的方法是在必要時動態擴展目標變量:

#include <stdlib.h>
#include <string.h>
#include <errno.h>

const size_t size_delta = 2; /* Adust to any value > 0 */

int string_to_tokens(char * s, const char * delimiters, char *** pptoken)
{
  int result = 0; /* Be optimistic. */
  int errno_saved = 0;

  if (NULL == s || NULL == pptoken)
  {
    /* Invalid input */
    result = -1;
    errno_saved = EINVAL;
  }
  else
  {
    size_t tokens = 0;
    size_t size = 1;

    *pptoken = malloc(size * sizeof **pptoken);
    if (NULL == *pptoken)
    {
      errno_saved = errno;
      result = -1;
    }
    else
    {
      (*pptoken)[tokens] = strtok(s, delimiters);

      while (NULL != (*pptoken)[tokens])
      {
        ++tokens;
        if (tokens >= size)
        {
          size += size_delta;

          {
            void * pv = realloc(*pptoken, size * sizeof **pptoken);
            if (NULL == pv)
            {
              errno_saved = errno;
              result = -1;
              break;
            } 

            *pptoken = pv;
          }
        }
        char * pc = strtok(NULL, delimiters);
        (*pptoken)[tokens] = pc;
      }
    }

    if (0 != errno_saved)
    {
      free(*pptoken);
      errno = errno_saved;
    }
  }

  return result;
}

上述解決方案的缺點是

  • 它通過在找到定界符'\\0'每個位置放置一個'\\0'來修改源數組s內容。
  • 結果指針數組的元素引用源數組

要解決此問題,只需確保您的代碼不關心(;-),或將tokeniser擴展到1st,即可創建工作副本並復制找到的令牌:

int string_to_tokens_nondistructive(const char * s, const char * delimiters, char *** pptoken)
{
  int result = 0; /* Be optimistic. */

  if (NULL == s || NULL == pptoken)
  {
    /* Invalid input */
    result = -1;
    errno = EINVAL;
  }
  else
  {
    int errno_saved = 0;
    char * w = strdup(s);

    if (NULL == w)
    {
      errno_saved = errno;
      result = 01;
    }
    else
    {
      size_t tokens = 0;
      size_t size = 1;
      *pptoken = malloc(size * sizeof **pptoken);

      char * p = strtok(w, delimiters);

      while (NULL != p)
      {
        (*pptoken)[tokens] = strdup(p);
        if (NULL == (*pptoken)[tokens])
        {
          errno_saved = errno;
          result = -1;
          break;
        } 

        ++tokens;

        if (tokens >= size)
        {
          size += size_delta;

          {
            void * pv = realloc(*pptoken, size * sizeof **pptoken);
            if (NULL == pv)
            {
              errno_saved = errno;
              result = -1;
              break;
            } 

            *pptoken = pv;
          }
        }

        p = strtok(NULL, delimiters);
      }

      (*pptoken)[tokens] = NULL;

      free(w);
    }

    if (0 != errno_saved)
    {
      free(*pptoken);
      errno = errno_saved;
    }
  }

  return result;
}

像這樣使用它們:

#include <stdlib.h>
#include <stdio.h>

int string_to_tokens(char * s, const char * delimiters, char *** pptoken);

void print_tokens(char * const * ptoken)
{
  char * const * p = ptoken;
  while (NULL != *p)
  {
    puts(*p);
    ++p;
  }
}

void hexdump_chararray(const char * a, size_t s)
{
  size_t i = 0;
  while (i < s)
  {
    printf("%02hhx ", a[i]);
    ++i;
    if (0 == (i % 16))
    {
      fputc('\n', stdout);
    }
  }

  if (0 != (i % 16))
  {
    fputc('\n', stdout);
  }
}

int main(void)
{
  char ** tokens;

  {
    char s[] = "Lorem ipsum dolor sit amet, ...";

    hexdump_chararray(s, sizeof s);

    if (-1 == string_to_tokens(s, ", ", &tokens))
    {
      perror("string_to_tokens() failed");
      exit(EXIT_FAILURE);
    }

    print_tokens(tokens);

    hexdump_chararray(s, sizeof s);

    free(tokens);
  }

  {
    char s[] = "Lorem ipsum dolor sit amet, ...";

    hexdump_chararray(s, sizeof s);

    if (-1 == string_to_tokens_nondistructive(s, ", ", &tokens))
    {
      perror("string_to_tokens_nondistructive() failed");
      exit(EXIT_FAILURE);
    }

    print_tokens(tokens);

    hexdump_chararray(s, sizeof s);

    {
      char ** p = tokens;
      while (NULL != *p)
      {
        free(*p);
        ++p;
      }
    }

    free(tokens);
  }
}

好的,我的方法是使用strtok_r (此代碼假定strdup()strtok_r可用strdup()返回以NULL結尾的令牌指針數組。此代碼假定省略了正確的標頭和所有錯誤檢查,以使滾動條出現):

char **tokenizeString( const char *in, const char *tokens )
{
    char *copy = strdup( in );
    char **array = malloc( sizeof( *array ) );

    // use this for strtok_r(), set to NULL after first use
    char *loopPtr = copy;
    char *savePtr;

    // explicitly break loop when strtok_r()
    // returns NULL (will cause last element
    // of array to be NULL)
    for ( int ii = 0;; ii++ )
    {
        array [ ii ] = strtok_r( loopPtr, tokens, &savePtr );
        if ( !array[ ii ] ) break;
        loopPtr = null; 

        // when ii is zero, we need a two-element array for
        // the next loop iteration
        array = realloc( array, ( ii + 2 ) * sizeof( *tmp ) );
    }
    return( array );
}

void freeTokenizedStringArray( char **array )
{
    free( *array );
    free( array );
}

直接在循環中使用copy而不是loopPtr ,並且在調用strtok_r()之后將copy設置為NULL會更短,但是在我看來,使用loopPtr使代碼的意圖更加明顯。 這對我來說有點太密集了:

char **tokenizeString( const char *in, const char *tokens )
{
    char *copy = strdup( in );
    char **array = malloc( sizeof( *array ) );
    char *savePtr;

    // explicitly break loop when strtok_r()
    // returns NULL (will cause last element
    // of array to be NULL)
    for ( int ii = 0;; ii++ )
    {
        array [ ii ] = strtok_r( copy, tokens, &savePtr );
        if ( !array[ ii ] ) break;
        copy = null; 

        // when ii is zero, we need a two-element array for
        // the next loop iteration
        array = realloc( array, ( ii + 2 ) * sizeof( *tmp ) );
    }
    return( array );
}

在我看來,這太依賴於strtok_r()工作原理的詳細知識,對於將來嘗試維護代碼的未來程序員,我並不想有效地要求它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM