Розділена рядок з роздільниками в С


155

Як написати функцію для розбиття та повернення масиву для рядка з роздільниками на мові програмування C?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');

25
Ви можете використовувати strtokфункцію зі стандартної бібліотеки, щоб досягти того самого.
Даніель Каміль Козар


Коментар ... Ключовим моментом strtok()сімейної функції є розуміння static variablesС., тобто як вони поводяться між послідовним викликом функції, в якому вони використовуються. Дивіться мій код нижче
fnisi

Відповіді:


165

Ви можете використовувати strtok()функцію для розділення рядка (та вкажіть роздільник, який слід використовувати). Зауважте, що strtok()буде змінено рядок, переданий до нього. Якщо оригінальний рядок потрібен в іншому місці, зробіть його копію та передайте копію strtok().

Редагувати:

Приклад (зауважте, він не обробляє послідовні роздільники, наприклад, "JAN ,,, FEB, MAR"):

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

char** str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Count how many elements will be extracted. */
    while (*tmp)
    {
        if (a_delim == *tmp)
        {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;

    result = malloc(sizeof(char*) * count);

    if (result)
    {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token)
        {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }

    return result;
}

int main()
{
    char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    char** tokens;

    printf("months=[%s]\n\n", months);

    tokens = str_split(months, ',');

    if (tokens)
    {
        int i;
        for (i = 0; *(tokens + i); i++)
        {
            printf("month=[%s]\n", *(tokens + i));
            free(*(tokens + i));
        }
        printf("\n");
        free(tokens);
    }

    return 0;
}

Вихід:

$ ./main.exe
months=[JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC]

month=[JAN]
month=[FEB]
month=[MAR]
month=[APR]
month=[MAY]
month=[JUN]
month=[JUL]
month=[AUG]
month=[SEP]
month=[OCT]
month=[NOV]
month=[DEC]

60
Привіт! на сторінці strtokлюдини позначено як застаріле strsep(3).
osgx

4
Оскільки це може бути канонічним питанням / відповіддю щодо переповнення стека для цього, чи не існує певних застережень щодо багатопотокової передачі за допомогою strtok?
Пітер Мортенсен

3
@osgx Відповідно до цієї сторінки, strsepвона є заміною для портативності strtok, але strtokє кращою. Отже, якщо вам не потрібна підтримка порожніх полів або розділення декількох рядків одночасно, strtokце кращий вибір.

4
@Dojo: Це пам'ятає; це одна з причин, що це проблематично. Краще використовувати strtok_s()(Microsoft, C11 додаток K, необов’язково) або strtok_r()(POSIX), ніж звичайний strtok(). Звичайне strtok()зло в роботі бібліотеки. Жодна функція, яка викликає функцію бібліотеки, може бути не використана strtok()в той час, і жодна функція, викликана функцією бібліотеки, не може викликати strtok().
Джонатан Леффлер

3
Лише зауваження, яке strtok()не є безпечним для потоків (з причин, які згадував @JonathanLeffler), а тому вся ця функція не є безпечною для потоків. Якщо ви спробуєте використати це у захищеному середовищі, ви отримаєте нестабільні та непередбачувані результати. Заміна strtok()для strtok_r()виправлення цієї проблеми.
Шон Ш

70

Я думаю, що strsepце все ще найкращий інструмент для цього:

while ((token = strsep(&str, ","))) my_fn(token);

Це буквально один рядок, який розбиває рядок.

Додаткові дужки є стилістичним елементом, який вказує на те, що ми навмисно перевіряємо результат завдання, а не оператор рівності ==.

Щоб ця схема працювала, tokenі strобидва мають тип char *. Якщо ви почали з літерального рядка, то спершу вам потрібно зробити його копію:

// More general pattern:
const char *my_str_literal = "JAN,FEB,MAR";
char *token, *str, *tofree;

tofree = str = strdup(my_str_literal);  // We own str's memory now.
while ((token = strsep(&str, ","))) my_fn(token);
free(tofree);

Якщо два роздільники відображаються разом str, ви отримаєте tokenзначення, яке є порожнім рядком. Значення strмодифікується тим, що кожен роздільник, що зустрічається, перезаписується нульовим байтом - ще одна вагома причина скопіювати рядок, який розбирається спочатку.

У коментарі хтось припустив, що strtokце краще, ніж strsepтому strtok, що більш портативний. Ubuntu та Mac OS X мають strsep; можна впевнитись, що це роблять і інші системи Unixy. Windows не вистачає strsep, але він має такий спосіб, strbrkякий дозволяє цю коротку та милу strsepзаміну:

char *strsep(char **stringp, const char *delim) {
  if (*stringp == NULL) { return NULL; }
  char *token_start = *stringp;
  *stringp = strpbrk(token_start, delim);
  if (*stringp) {
    **stringp = '\0';
    (*stringp)++;
  }
  return token_start;
}

Ось гарне пояснення strsepпроти strtok. Про плюси і мінуси можна судити суб'єктивно; однак, я думаю, що це показовий знак, який strsepбув розроблений як заміна strtok.


3
Точніше щодо портативності: це не POSIX 7 , а похідний BSD та реалізований на glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Я щойно збирався запитати ... C Pelle має strdup (), але немає strsep ().
rdtsc

1
чому tofreeтой, хто вільний, а ні str?
Сдліон

1
Ви не можете звільнитись, strоскільки її значення можна змінити дзвінками на strsep(). Значення tofreeпослідовно вказує на початок пам'яті, яку ви хочете звільнити.
Тайлер

26

Цей рядок токенізатора повинен підвести вас у правильному напрямку.

int main(void) {
  char st[] ="Where there is will, there is a way.";
  char *ch;
  ch = strtok(st, " ");
  while (ch != NULL) {
  printf("%s\n", ch);
  ch = strtok(NULL, " ,");
  }
  getch();
  return 0;
}

13

Наведений нижче метод зробить всю роботу (розподіл пам'яті, підрахунок довжини) для вас. Більше інформації та опису можна знайти тут - Реалізація методу Java String.split () для розділення рядка C

int split (const char *str, char c, char ***arr)
{
    int count = 1;
    int token_len = 1;
    int i = 0;
    char *p;
    char *t;

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
            count++;
        p++;
    }

    *arr = (char**) malloc(sizeof(char*) * count);
    if (*arr == NULL)
        exit(1);

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
        {
            (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
            if ((*arr)[i] == NULL)
                exit(1);

            token_len = 0;
            i++;
        }
        p++;
        token_len++;
    }
    (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
    if ((*arr)[i] == NULL)
        exit(1);

    i = 0;
    p = str;
    t = ((*arr)[i]);
    while (*p != '\0')
    {
        if (*p != c && *p != '\0')
        {
            *t = *p;
            t++;
        }
        else
        {
            *t = '\0';
            i++;
            t = ((*arr)[i]);
        }
        p++;
    }

    return count;
}

Як ним користуватися:

int main (int argc, char ** argv)
{
    int i;
    char *s = "Hello, this is a test module for the string splitting.";
    int c = 0;
    char **arr = NULL;

    c = split(s, ' ', &arr);

    printf("found %d tokens.\n", c);

    for (i = 0; i < c; i++)
        printf("string #%d: %s\n", i, arr[i]);

    return 0;
}

4
Гм Тризірковий програміст :)) Це звучить цікаво.
Мічі

Коли я це роблю, він або додає занадто багато останнього маркера, або виділяє йому занадто багато пам'яті. Це вихід: found 10 tokens. string #0: Hello, string #1: this string #2: is string #3: a string #4: test string #5: module string #6: for string #7: the string #8: string string #9: splitting.¢
KeizerHarm

2
Цей приклад має кілька витоків пам'яті. Для тих, хто читає це, не використовуйте такий підхід. Натомість віддайте перевагу підходу токенізації strtok або strsep.
Jorma Rebane

7

Ось два мої центи:

int split (const char *txt, char delim, char ***tokens)
{
    int *tklen, *t, count = 1;
    char **arr, *p = (char *) txt;

    while (*p != '\0') if (*p++ == delim) count += 1;
    t = tklen = calloc (count, sizeof (int));
    for (p = (char *) txt; *p != '\0'; p++) *p == delim ? *t++ : (*t)++;
    *tokens = arr = malloc (count * sizeof (char *));
    t = tklen;
    p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
    while (*txt != '\0')
    {
        if (*txt == delim)
        {
            p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
            txt++;
        }
        else *p++ = *txt++;
    }
    free (tklen);
    return count;
}

Використання:

char **tokens;
int count, i;
const char *str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

count = split (str, ',', &tokens);
for (i = 0; i < count; i++) printf ("%s\n", tokens[i]);

/* freeing tokens */
for (i = 0; i < count; i++) free (tokens[i]);
free (tokens);

3
о бой, три покажчики! Мені вже страшно використовувати його, хай це тільки я, я не дуже добре вказую на c.
Хафіз Темурі

Спасибі людину, все вище відповіді на strtok не працювали в моєму випадку навіть після багатьох зусиль, і ваш код працює як шарм!
hmmftg

4

У наведеному вище прикладі був би спосіб повернути масив нульових завершених рядків (як вам потрібно) на місце в рядку. Це не дозволить передати буквальний рядок, оскільки це має бути змінено функцією:

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

char** str_split( char* str, char delim, int* numSplits )
{
    char** ret;
    int retLen;
    char* c;

    if ( ( str == NULL ) ||
        ( delim == '\0' ) )
    {
        /* Either of those will cause problems */
        ret = NULL;
        retLen = -1;
    }
    else
    {
        retLen = 0;
        c = str;

        /* Pre-calculate number of elements */
        do
        {
            if ( *c == delim )
            {
                retLen++;
            }

            c++;
        } while ( *c != '\0' );

        ret = malloc( ( retLen + 1 ) * sizeof( *ret ) );
        ret[retLen] = NULL;

        c = str;
        retLen = 1;
        ret[0] = str;

        do
        {
            if ( *c == delim )
            {
                ret[retLen++] = &c[1];
                *c = '\0';
            }

            c++;
        } while ( *c != '\0' );
    }

    if ( numSplits != NULL )
    {
        *numSplits = retLen;
    }

    return ret;
}

int main( int argc, char* argv[] )
{
    const char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

    char* strCpy;
    char** split;
    int num;
    int i;

    strCpy = malloc( strlen( str ) * sizeof( *strCpy ) );
    strcpy( strCpy, str );

    split = str_split( strCpy, ',', &num );

    if ( split == NULL )
    {
        puts( "str_split returned NULL" );
    }
    else
    {
        printf( "%i Results: \n", num );

        for ( i = 0; i < num; i++ )
        {
            puts( split[i] );
        }
    }

    free( split );
    free( strCpy );

    return 0;
}

Мабуть, є більш акуратний спосіб це зробити, але ви отримуєте ідею.


3

Ця функція приймає рядок char * і розбиває її відмежувачем. Підряд може бути декількома роздільниками. Зауважте, що функція змінює початковий рядок. Спершу потрібно зробити копію оригінального рядка, якщо вам потрібно, щоб оригінал не змінився. Ця функція не використовує жодних викликів функції cstring, тому вона може бути трохи швидшою, ніж інші. Якщо ви не переймаєтесь розподілом пам’яті, ви можете виділити під_рядки у верхній частині функції з розміром strlen (src_str) / 2 і (як і вказана «версія» c ++) пропустити нижню половину функції. Якщо це зробити, функція зводиться до O (N), але оптимізованим способом пам'яті способом, показаним нижче, є O (2N).

Функція:

char** str_split(char *src_str, const char deliminator, size_t &num_sub_str){
  //replace deliminator's with zeros and count how many
  //sub strings with length >= 1 exist
  num_sub_str = 0;
  char *src_str_tmp = src_str;
  bool found_delim = true;
  while(*src_str_tmp){
    if(*src_str_tmp == deliminator){
      *src_str_tmp = 0;
      found_delim = true;
    }
    else if(found_delim){ //found first character of a new string
      num_sub_str++;
      found_delim = false;
      //sub_str_vec.push_back(src_str_tmp); //for c++
    }
    src_str_tmp++;
  }
  printf("Start - found %d sub strings\n", num_sub_str);
  if(num_sub_str <= 0){
    printf("str_split() - no substrings were found\n");
    return(0);
  }

  //if you want to use a c++ vector and push onto it, the rest of this function
  //can be omitted (obviously modifying input parameters to take a vector, etc)

  char **sub_strings = (char **)malloc( (sizeof(char*) * num_sub_str) + 1);
  const char *src_str_terminator = src_str_tmp;
  src_str_tmp = src_str;
  bool found_null = true;
  size_t idx = 0;
  while(src_str_tmp < src_str_terminator){
    if(!*src_str_tmp) //found a NULL
      found_null = true;
    else if(found_null){
      sub_strings[idx++] = src_str_tmp;
      //printf("sub_string_%d: [%s]\n", idx-1, sub_strings[idx-1]);
      found_null = false;
    }
    src_str_tmp++;
  }
  sub_strings[num_sub_str] = NULL;

  return(sub_strings);
}

Як ним користуватися:

  char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char *str = strdup(months);
  size_t num_sub_str;
  char **sub_strings = str_split(str, ',', num_sub_str);
  char *endptr;
  if(sub_strings){
    for(int i = 0; sub_strings[i]; i++)
      printf("[%s]\n", sub_strings[i]);
  }
  free(sub_strings);
  free(str);

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

/**
 *  splits str on delim and dynamically allocates an array of pointers.
 *
 *  On error -1 is returned, check errno
 *  On success size of array is returned, which may be 0 on an empty string
 *  or 1 if no delim was found.  
 *
 *  You could rewrite this to return the char ** array instead and upon NULL
 *  know it's an allocation problem but I did the triple array here.  Note that
 *  upon the hitting two delim's in a row "foo,,bar" the array would be:
 *  { "foo", NULL, "bar" } 
 * 
 *  You need to define the semantics of a trailing delim Like "foo," is that a
 *  2 count array or an array of one?  I choose the two count with the second entry
 *  set to NULL since it's valueless.
 *  Modifies str so make a copy if this is a problem
 */
int split( char * str, char delim, char ***array, int *length ) {
  char *p;
  char **res;
  int count=0;
  int k=0;

  p = str;
  // Count occurance of delim in string
  while( (p=strchr(p,delim)) != NULL ) {
    *p = 0; // Null terminate the deliminator.
    p++; // Skip past our new null
    count++;
  }

  // allocate dynamic array
  res = calloc( 1, count * sizeof(char *));
  if( !res ) return -1;

  p = str;
  for( k=0; k<count; k++ ){
    if( *p ) res[k] = p;  // Copy start of string
    p = strchr(p, 0 );    // Look for next null
    p++; // Start of next string
  }

  *array = res;
  *length = count;

  return 0;
}

char str[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,";

int main() {
  char **res;
  int k=0;
  int count =0;
  int rc;

  rc = split( str, ',', &res, &count );
  if( rc ) {
    printf("Error: %s errno: %d \n", strerror(errno), errno);
  }

  printf("count: %d\n", count );
  for( k=0; k<count; k++ ) {
    printf("str: %s\n", res[k]);
  }

  free(res );
  return 0;
}

3

Нижче наводиться моя strtok()реалізація з бібліотеки zString . zstring_strtok()відрізняється від стандартної бібліотеки strtok()тим, як вона обробляє послідовні роздільники.

Просто подивіться на код нижче, впевнений, що ви отримаєте уявлення про те, як він працює (я намагався використати якомога більше коментарів)

char *zstring_strtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;       /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

Нижче наведено приклад використання ...

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zstring_strtok(s,","));
      printf("2 %s\n",zstring_strtok(NULL,","));
      printf("3 %s\n",zstring_strtok(NULL,","));
      printf("4 %s\n",zstring_strtok(NULL,","));
      printf("5 %s\n",zstring_strtok(NULL,","));
      printf("6 %s\n",zstring_strtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

Бібліотеку можна завантажити з Github https://github.com/fnoyanisi/zString


хороший! ось що я шукав.
Костя Кім

3

Я думаю, що наступне рішення є ідеальним:

  • Не знищує рядок джерела
  • Повторний абітурієнт - тобто ви можете сміливо телефонувати з будь-якого місця в одній або декількох потоках
  • Портативний
  • Правильно поводиться з декількома роздільниками
  • Швидкий та ефективний

Пояснення коду:

  1. Визначте структуру tokenдля зберігання адреси та довжини лексем
  2. Виділіть достатню кількість пам'яті для них у гіршому випадку, коли strце повністю складається з роздільників, щоб було strlen(str) + 1 лексеми, всі вони порожні рядки
  3. Сканування strзапису адреси та довжини кожного маркера
  4. Використовуйте це, щоб виділити вихідний масив правильного розміру, включаючи додатковий простір для NULLвартості вартості
  5. Виділіть, скопіюйте та додайте жетони, використовуючи інформацію про початок та довжину - використовуйте так, memcpyяк це швидше, ніж strcpyми знаємо довжини
  6. Звільніть адресу та маркер довжини
  7. Повернути масив жетонів
typedef struct {
    const char *start;
    size_t len;
} token;

char **split(const char *str, char sep)
{
    char **array;
    unsigned int start = 0, stop, toks = 0, t;
    token *tokens = malloc((strlen(str) + 1) * sizeof(token));
    for (stop = 0; str[stop]; stop++) {
        if (str[stop] == sep) {
            tokens[toks].start = str + start;
            tokens[toks].len = stop - start;
            toks++;
            start = stop + 1;
        }
    }
    /* Mop up the last token */
    tokens[toks].start = str + start;
    tokens[toks].len = stop - start;
    toks++;
    array = malloc((toks + 1) * sizeof(char*));
    for (t = 0; t < toks; t++) {
        /* Calloc makes it nul-terminated */
        char *token = calloc(tokens[t].len + 1, 1);
        memcpy(token, tokens[t].start, tokens[t].len);
        array[t] = token;
    }
    /* Add a sentinel */
    array[t] = NULL; 
    free(tokens);
    return array;
}

Примітка malloc перевірки пропущена на стислість.

Взагалі я б не повертав масив char *покажчиків з розбитої функції, як це, оскільки це покладає велику відповідальність на абонента, щоб правильно їх звільнити. Інтерфейс Я віддаю перевагу , щоб дозволити абоненту передати функцію зворотного виклику і називати це для кожних маркерів, як я описав тут: розбити рядок в C .


Сканування сепараторів двічі, ймовірно, доцільніше, ніж виділення потенційно великого масиву token.
chqrlie

2

Спробуйте скористатися цим.

char** strsplit(char* str, const char* delim){
    char** res = NULL;
    char*  part;
    int i = 0;

    char* aux = strdup(str);

    part = strdup(strtok(aux, delim));

    while(part){
        res = (char**)realloc(res, (i + 1) * sizeof(char*));
        *(res + i) = strdup(part);

        part = strdup(strtok(NULL, delim));
        i++;
    }

    res = (char**)realloc(res, i * sizeof(char*));
    *(res + i) = NULL;

    return res;
}

2

Цей оптимізований метод створює (або оновлює наявний) масив покажчиків у * результаті та повертає кількість елементів у * count.

Використовуйте "max", щоб вказати максимальну кількість рядків, які ви очікуєте (коли ви вказуєте існуючий масив або будь-який інший реасеон), інакше встановіть його на 0

Для порівняння зі списком роздільників, визначте розділення як знак * та замініть рядок:

if (str[i]==delim) {

з двома наступними рядками:

 char *c=delim; while(*c && *c!=str[i]) c++;
 if (*c) {

Насолоджуйтесь

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

char **split(char *str, size_t len, char delim, char ***result, unsigned long *count, unsigned long max) {
  size_t i;
  char **_result;

  // there is at least one string returned
  *count=1;

  _result= *result;

  // when the result array is specified, fill it during the first pass
  if (_result) {
    _result[0]=str;
  }

  // scan the string for delimiter, up to specified length
  for (i=0; i<len; ++i) {

    // to compare against a list of delimiters,
    // define delim as a string and replace 
    // the next line:
    //     if (str[i]==delim) {
    //
    // with the two following lines:
    //     char *c=delim; while(*c && *c!=str[i]) c++;
    //     if (*c) {
    //       
    if (str[i]==delim) {

      // replace delimiter with zero
      str[i]=0;

      // when result array is specified, fill it during the first pass
      if (_result) {
        _result[*count]=str+i+1;
      }

      // increment count for each separator found
      ++(*count);

      // if max is specified, dont go further
      if (max && *count==max)  {
        break;
      }

    }
  }

  // when result array is specified, we are done here
  if (_result) {
    return _result;
  }

  // else allocate memory for result
  // and fill the result array                                                                                    

  *result=malloc((*count)*sizeof(char*));
  if (!*result) {
    return NULL;
  }
  _result=*result;

  // add first string to result
  _result[0]=str;

  // if theres more strings
  for (i=1; i<*count; ++i) {

    // find next string
    while(*str) ++str;
    ++str;

    // add next string to result
    _result[i]=str;

  }

  return _result;
}  

Приклад використання:

#include <stdio.h>

int main(int argc, char **argv) {
  char *str="JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char **result=malloc(6*sizeof(char*));
  char **result2=0;
  unsigned long count;
  unsigned long count2;
  unsigned long i;

  split(strdup(str),strlen(str),',',&result,&count,6);
  split(strdup(str),strlen(str),',',&result2,&count2,0);

  if (result)
  for (i=0; i<count; ++i) {
    printf("%s\n",result[i]);
  }

  printf("\n");

  if (result2)
  for (i=0; i<count2; ++i) {
    printf("%s\n", result2[i]);
  }

  return 0;

}

2

Моя версія:

int split(char* str, const char delimeter, char*** args) {
    int cnt = 1;
    char* t = str;

    while (*t == delimeter) t++;

    char* t2 = t;
    while (*(t2++))
        if (*t2 == delimeter && *(t2 + 1) != delimeter && *(t2 + 1) != 0) cnt++;

    (*args) = malloc(sizeof(char*) * cnt);

    for(int i = 0; i < cnt; i++) {
        char* ts = t;
        while (*t != delimeter && *t != 0) t++;

        int len = (t - ts + 1);
        (*args)[i] = malloc(sizeof(char) * len);
        memcpy((*args)[i], ts, sizeof(char) * (len - 1));
        (*args)[i][len - 1] = 0;

        while (*t == delimeter) t++;
    }

    return cnt;
}

2

Це функція розбиття рядків, яка може обробляти багатознакові роздільники. Зверніть увагу, що якщо роздільник більше, ніж розділений рядок, то bufferі stringLengthsбуде встановлено (void *) 0, і numStringsбуде встановлено 0.

Цей алгоритм перевірений і працює. (Відмова від відповідальності: він не перевірявся на рядки, що не належать до ASCII, і передбачає, що абонент дав дійсні параметри)

void splitString(const char *original, const char *delimiter, char ** * buffer, int * numStrings, int * * stringLengths){
    const int lo = strlen(original);
    const int ld = strlen(delimiter);
    if(ld > lo){
        *buffer = (void *)0;
        *numStrings = 0;
        *stringLengths = (void *)0;
        return;
    }

    *numStrings = 1;

    for(int i = 0;i < (lo - ld);i++){
        if(strncmp(&original[i], delimiter, ld) == 0) {
            i += (ld - 1);
            (*numStrings)++;
        }
    }

    *stringLengths = (int *) malloc(sizeof(int) * *numStrings);

    int currentStringLength = 0;
    int currentStringNumber = 0;
    int delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(i < (lo - ld)){
            if(strncmp(&original[i], delimiter, ld) == 0){
                (*stringLengths)[currentStringNumber] = currentStringLength;
                currentStringNumber++;
                currentStringLength = 0;
                delimiterTokenDecrementCounter = ld - 1;
            } else {
                currentStringLength++;
            }
        } else {
            currentStringLength++;
        }

        if(i == (lo - 1)){
            (*stringLengths)[currentStringNumber] = currentStringLength;
        }
    }

    *buffer = (char **) malloc(sizeof(char *) * (*numStrings));
    for(int i = 0;i < *numStrings;i++){
        (*buffer)[i] = (char *) malloc(sizeof(char) * ((*stringLengths)[i] + 1));
    }

    currentStringNumber = 0;
    currentStringLength = 0;
    delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(currentStringLength >= (*stringLengths)[currentStringNumber]){
            (*buffer)[currentStringNumber][currentStringLength] = 0;
            delimiterTokenDecrementCounter = ld - 1;
            currentStringLength = 0;
            currentStringNumber++;
        } else {
            (*buffer)[currentStringNumber][currentStringLength] = (char)original[i];
            currentStringLength++;
        }
    }
    buffer[currentStringNumber][currentStringLength] = 0;
}

Приклад коду:

int main(){
    const char *string = "STRING-1 DELIM string-2 DELIM sTrInG-3";
    char **buffer;
    int numStrings;
    int * stringLengths;

    splitString(string, " DELIM ", &buffer, &numStrings, &stringLengths);

    for(int i = 0;i < numStrings;i++){
        printf("String: %s\n", buffer[i]);
    }
}

Бібліотеки:

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

Як я називаю це від головного? Я не знаю, що передати в буфер.
Еймон Фурньє

Логіка розподілу помилкова. realloc () повертає новий покажчик, і ви відкинете повернене значення. Немає належного способу повернення нового прототипу вказівника пам'яті - не слід змінювати прототип функції, щоб прийняти розмір виділеного bufferта залишити розподіл для абонента, обробити елементи максимального розміру.
Олексій

@Alex Виправлено, повністю переписано та протестовано. Примітка: не впевнений, чи це буде працювати для не-ASCII чи ні.
Електра

Для початківців це не код С. І навіщо ви передавати покажчики за фактичною посиланням на C ++?
Каміколо

@Kamiccolo Вибачте, як саме це не код C? Крім того, чому тут є проблема передачі покажчиків шляхом посилання?
Електра

1

Мій підхід полягає в тому, щоб сканувати рядок і дозволити покажчикам вказувати на кожного символу після розмежувачів (і першого символу), і одночасно призначити види розмежувача в рядку на '\ 0'.
Спочатку зробіть копію оригінального рядка (оскільки він є постійним), потім отримайте кількість розщеплень, сканувавши його, передайте його параметру вказівника len . Після цього наведіть перший вказівник на результат вказівника на рядок копіювання, після чого скануйте рядок копіювання: як тільки зустрінеться з роздільником, призначте його на '\ 0', таким чином попередній рядок результату припиняється, а наступний вказівник рядка результатів на наступний вказівник символів

char** split(char* a_str, const char a_delim, int* len){
    char* s = (char*)malloc(sizeof(char) * strlen(a_str));
    strcpy(s, a_str);
    char* tmp = a_str;
    int count = 0;
    while (*tmp != '\0'){
        if (*tmp == a_delim) count += 1;
        tmp += 1;
    }
    *len = count;
    char** results = (char**)malloc(count * sizeof(char*));
    results[0] = s;
    int i = 1;
    while (*s!='\0'){
        if (*s == a_delim){
            *s = '\0';
            s += 1;
            results[i++] = s;
        }
        else s += 1;
    }
    return results;
}

Цей метод неправильний. Мене просто видалили цю публікацію, але потім я зрозумів, що це може бути цікавим для когось із вас.
metalcrash

1

Мій код (перевірено):

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

int dtmsplit(char *str, const char *delim, char ***array, int *length ) {
  int i=0;
  char *token;
  char **res = (char **) malloc(0 * sizeof(char *));

  /* get the first token */
   token = strtok(str, delim);
   while( token != NULL ) 
   {
        res = (char **) realloc(res, (i + 1) * sizeof(char *));
        res[i] = token;
        i++;
      token = strtok(NULL, delim);
   }
   *array = res;
   *length = i;
  return 1;
}

int main()
{
    int i;
    int c = 0;
    char **arr = NULL;

    int count =0;

    char str[80] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    c = dtmsplit(str, ",", &arr, &count);
    printf("Found %d tokens.\n", count);

    for (i = 0; i < count; i++)
        printf("string #%d: %s\n", i, arr[i]);

   return(0);
}

Результат:

Found 12 tokens.
string #0: JAN
string #1: FEB
string #2: MAR
string #3: APR
string #4: MAY
string #5: JUN
string #6: JUL
string #7: AUG
string #8: SEP
string #9: OCT
string #10: NOV
string #11: DEC

1
Майте на увазі, що функція strtok змінює рядок 'str', до якого було застосовано!
SchLx

1

Explode & implode - початковий рядок залишається неушкодженим, динамічне розподілення пам'яті

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

typedef struct
{
    uintptr_t   ptr;
    int         size;
} token_t;

int explode(char *str, int slen, const char *delimiter, token_t **tokens)
{
    int i = 0, c1 = 0, c2 = 0;

    for(i = 0; i <= slen; i++)
    {
            if(str[i] == *delimiter)
            {
                c1++;
            }
    }

    if(c1 == 0)
    {
            return -1;
    }

    *tokens = (token_t*)calloc((c1 + 1), sizeof(token_t));
    ((*tokens)[c2]).ptr = (uintptr_t)str;

    i = 0; 
    while(i <= slen)
    {
        if((str[i] == *delimiter) || (i == slen))
        {
                ((*tokens)[c2]).size = (int)((uintptr_t)&(str[i]) - (uintptr_t)(((*tokens)[c2]).ptr));
                if(i < slen)
                {
                    c2++;
                    ((*tokens)[c2]).ptr = (uintptr_t)&(str[i + 1]);
                }
        }
        i++;
    }
    return (c1 + 1);
}

char* implode(token_t *tokens, int size, const char *delimiter)
{
    int     i, len = 0;
    char    *str;

    for(i = 0; i < len; i++)
    {
        len += tokens[i].size + 1;
    }

    str = (char*)calloc(len, sizeof(char));

    len = 0;
    for(i = 0; i < size; i++)
    {
        memcpy((void*)&str[len], (void*)tokens[i].ptr, tokens[i].size);
        len += tokens[i].size;
        str[(len++)] = *delimiter;
    }

    str[len - 1] = '\0';

    return str;
}

Використання:

int main(int argc, char **argv)
{
    int         i, c;
    char        *exp = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    token_t     *tokens;
    char        *imp;

    printf("%s\n", exp);

    if((c = explode(exp, strlen(exp), ",", &tokens)) > 0)
    {
        imp = implode(tokens, c, ",");
        printf("%s\n", imp);

        for(i = 0; i < c; i++)
        {
            printf("%.*s, %d\n", tokens[i].size, (char*)tokens[i].ptr, tokens[i].size);
        }
    }

    free((void*)tokens);
    free((void*)imp);
    return 0;
}

0

Якщо ви готові використовувати зовнішню бібліотеку, я не можу рекомендувати їх bstrlibдостатньо. Це вимагає трохи додаткових налаштувань, але простіше у використанні в довгостроковій перспективі.

Наприклад, розділіть рядок нижче, один спочатку створює a bstringз bfromcstr()викликом. (A bstring- обгортка навколо буфера символів). Далі розділіть рядок на коми, зберігаючи результат у a struct bstrList, який має поля qtyта масив entry, який є масивом bstrings.

bstrlibмає багато інших функцій для роботи на bstrings

Легкий як пиріг ...

#include "bstrlib.h"
#include <stdio.h>
int main() {
  int i;
  char *tmp = "Hello,World,sak";
  bstring bstr = bfromcstr(tmp);
  struct bstrList *blist = bsplit(bstr, ',');
  printf("num %d\n", blist->qty);
  for(i=0;i<blist->qty;i++) {
    printf("%d: %s\n", i, bstr2cstr(blist->entry[i], '_'));
  }

}

0

Ще одна відповідь (це було перенесено сюди ):

Спробуйте використовувати функцію strtok:

див. подробиці на цю тему тут чи тут

Тут питання полягає в тому, що вам потрібно негайно обробити words. Якщо ви хочете зберегти його в масиві, вам потрібно виділити correct sizeдля нього відьму невідомо.

Так, наприклад:

char **Split(char *in_text, char *in_sep)
{
    char **ret = NULL;
    int count = 0;
    char *tmp = strdup(in_text);
    char *pos = tmp;

    // This is the pass ONE: we count 
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        count++;
        pos = NULL;
    }

    // NOTE: the function strtok changes the content of the string! So we free and duplicate it again! 
    free(tmp);
    pos = tmp = strdup(in_text);

    // We create a NULL terminated array hence the +1
    ret = calloc(count+1, sizeof(char*));
    // TODO: You have to test the `ret` for NULL here

    // This is the pass TWO: we store
    count = 0;
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        ret[count] = strdup(pos);
        count++;
        pos = NULL;
    }
    free(tmp);

    return count;
}

// Use this to free
void Free_Array(char** in_array)
{
    char *pos = in_array;

    while (pos[0] != NULL)
    {
        free(pos[0]);
        pos++;

    }

    free(in_array);

}

Примітка . Ми використовуємо один і той же цикл і функцію для обчислення підрахунків (пропустити одну) і для виготовлення копій (пропустити дві), щоб уникнути проблем з розподілом.

Примітка 2 : Ви можете використовувати якусь іншу реалізацію strtok, причини якої згадуються в окремих публікаціях.

Ви можете використовувати це так:

int main(void)
{
  char **array = Split("Hello World!", " ");
  // Now you have the array
  // ...

  // Then free the memory
  Free_Array(array);
  array = NULL;
  return 0;
}

(Я не тестував його, тому, будь ласка, повідомте мене, якщо це не працює!)


0

Два питання навколо цього питання - це управління пам'яттю та безпека потоку. Як видно з численних публікацій, це непросте завдання здійснити без проблем у C. Я бажав рішення, яке полягає в наступному:

  • Нитка безпечна. (strtok не є безпечним для потоків)
  • Не використовує malloc або будь-які його похідні (щоб уникнути проблем із управлінням пам'яттю)
  • Перевіряє межі масиву в окремих полях (щоб уникнути помилок сегмента на невідомих даних)
  • Працює з багатобайтовими роздільниками поля (utf-8)
  • ігнорує додаткові поля у введенні
  • забезпечує м'яку процедуру помилок для недійсних довжин поля

Я запропонував рішення, яке відповідає всім цим критеріям. Мабуть, це трохи більше роботи з налаштуванням, ніж деякі інші рішення, розміщені тут, але я думаю, що на практиці додаткова робота варта того, щоб уникнути загальних підводних каменів інших рішень.

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

struct splitFieldType {
    char *field;
    int   maxLength;
};

typedef struct splitFieldType splitField;

int strsplit(splitField *fields, int expected, const char *input, const char *fieldSeparator, void (*softError)(int fieldNumber,int expected,int actual))  {
    int i;
    int fieldSeparatorLen=strlen(fieldSeparator);
    const char *tNext, *tLast=input;

    for (i=0; i<expected && (tNext=strstr(tLast, fieldSeparator))!=NULL; ++i) {
        int len=tNext-tLast;
        if (len>=fields[i].maxLength) {
            softError(i,fields[i].maxLength-1,len);
            len=fields[i].maxLength-1;
        }
        fields[i].field[len]=0;
        strncpy(fields[i].field,tLast,len);
        tLast=tNext+fieldSeparatorLen;
    }
    if (i<expected) {
        if (strlen(tLast)>fields[i].maxLength) {
            softError(i,fields[i].maxLength,strlen(tLast));
        } else {
            strcpy(fields[i].field,tLast);
        }
        return i+1;
    } else {
        return i;
    }
}


void monthSplitSoftError(int fieldNumber, int expected, int actual) {
    fprintf(stderr,"monthSplit: input field #%d is %d bytes, expected %d bytes\n",fieldNumber+1,actual,expected);
}


int main() {
  const char *fieldSeparator=",";
  const char *input="JAN,FEB,MAR,APRI,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR";

  struct monthFieldsType {
    char field1[4];
    char field2[4];
    char field3[4];
    char field4[4];
    char field5[4];
    char field6[4];
    char field7[4];
    char field8[4];
    char field9[4];
    char field10[4];
    char field11[4];
    char field12[4];
  } monthFields;

  splitField inputFields[12] = {
    {monthFields.field1,  sizeof(monthFields.field1)},
    {monthFields.field2,  sizeof(monthFields.field2)},
    {monthFields.field3,  sizeof(monthFields.field3)},
    {monthFields.field4,  sizeof(monthFields.field4)},
    {monthFields.field5,  sizeof(monthFields.field5)},
    {monthFields.field6,  sizeof(monthFields.field6)},
    {monthFields.field7,  sizeof(monthFields.field7)},
    {monthFields.field8,  sizeof(monthFields.field8)},
    {monthFields.field9,  sizeof(monthFields.field9)},
    {monthFields.field10, sizeof(monthFields.field10)},
    {monthFields.field11, sizeof(monthFields.field11)},
    {monthFields.field12, sizeof(monthFields.field12)}
  };

  int expected=sizeof(inputFields)/sizeof(splitField);

  printf("input data: %s\n", input);
  printf("expecting %d fields\n",expected);

  int ct=strsplit(inputFields, expected, input, fieldSeparator, monthSplitSoftError);

  if (ct!=expected) {
    printf("string split %d fields, expected %d\n", ct,expected);
  }

  for (int i=0;i<expected;++i) {
    printf("field %d: %s\n",i+1,inputFields[i].field);
  }

  printf("\n");
  printf("Direct structure access, field 10: %s", monthFields.field10);
}

Нижче наведено приклад компіляції та виведення. Зауважте, що у своєму прикладі я цілеспрямовано прописав "АПРИЛ", щоб ви могли бачити, як працює м'яка помилка.

$ gcc strsplitExample.c && ./a.out
input data: JAN,FEB,MAR,APRIL,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR
expecting 12 fields
monthSplit: input field #4 is 5 bytes, expected 3 bytes
field 1: JAN
field 2: FEB
field 3: MAR
field 4: APR
field 5: MAY
field 6: JUN
field 7: JUL
field 8: AUG
field 9: SEP
field 10: OCT
field 11: NOV
field 12: DEC

Direct structure access, field 10: OCT

Насолоджуйтесь!


0

Ось ще одна реалізація, яка буде безпечно функціонувати для токенізації рядково -літеральної відповідності прототипу, запитуваному у запитанні, повертаючи виділений вказівник на вказівник знаку char (наприклад char **). Рядок роздільника може містити кілька символів, а рядок введення може містити будь-яку кількість лексем. Усі розподіли та перерозподіли обробляються через POSIX mallocабо reallocбез нього strdup.

Початкова кількість виділених покажчиків контролюється NPTRSпостійною, і єдине обмеження полягає в тому, щоб воно було більше нуля. char **Повертаються містить сторожовий NULL після того, як останній маркер аналогічні *argv[]і в формі , використовуваної execv, execvpі execve.

Як і у випадку strtok()декількох послідовних роздільників, які розглядаються як окремий роздільник, так "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"буде розбиратися так, ніби ','розділяється лише один "MAY,JUN".

Функція нижче коментується по-черзі, і main()було додано коротке розділення місяців. Початкову кількість виділених покажчиків було встановлено, 2щоб примусити три перерозподілу під час токенізації вхідного рядка:

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

#define NPTRS 2     /* initial number of pointers to allocate (must be > 0) */

/* split src into tokens with sentinel NULL after last token.
 * return allocated pointer-to-pointer with sentinel NULL on success,
 * or NULL on failure to allocate initial block of pointers. The number
 * of allocated pointers are doubled each time reallocation required.
 */
char **strsplit (const char *src, const char *delim)
{
    int i = 0, in = 0, nptrs = NPTRS;       /* index, in/out flag, ptr count */
    char **dest = NULL;                     /* ptr-to-ptr to allocate/fill */
    const char *p = src, *ep = p;           /* pointer and end-pointer */

    /* allocate/validate nptrs pointers for dest */
    if (!(dest = malloc (nptrs * sizeof *dest))) {
        perror ("malloc-dest");
        return NULL;
    }
    *dest = NULL;   /* set first pointer as sentinel NULL */

    for (;;) {  /* loop continually until end of src reached */
        if (!*ep || strchr (delim, *ep)) {  /* if at nul-char or delimiter char */
            size_t len = ep - p;            /* get length of token */
            if (in && len) {                /* in-word and chars in token */
                if (i == nptrs - 1) {       /* used pointer == allocated - 1? */
                    /* realloc dest to temporary pointer/validate */
                    void *tmp = realloc (dest, 2 * nptrs * sizeof *dest);
                    if (!tmp) {
                        perror ("realloc-dest");
                        break;  /* don't exit, original dest still valid */
                    }
                    dest = tmp;             /* assign reallocated block to dest */
                    nptrs *= 2;             /* increment allocated pointer count */
                }
                /* allocate/validate storage for token */
                if (!(dest[i] = malloc (len + 1))) {
                    perror ("malloc-dest[i]");
                    break;
                }
                memcpy (dest[i], p, len);   /* copy len chars to storage */
                dest[i++][len] = 0;         /* nul-terminate, advance index */
                dest[i] = NULL;             /* set next pointer NULL */
            }
            if (!*ep)                       /* if at end, break */
                break;
            in = 0;                         /* set in-word flag 0 (false) */
        }
        else {  /* normal word char */
            if (!in)                        /* if not in-word */
                p = ep;                     /* update start to end-pointer */
            in = 1;                         /* set in-word flag 1 (true) */
        }
        ep++;   /* advance to next character */
    }

    return dest;
}

int main (void) {

    char *str = "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC",
        **tokens;                           /* pointer to pointer to char */

    if ((tokens = strsplit (str, ","))) {   /* split string into tokens */
        for (char **p = tokens; *p; p++) {  /* loop over filled pointers */
            puts (*p);
            free (*p);      /* don't forget to free allocated strings */
        }
        free (tokens);      /* and pointers */
    }
}

Приклад Використання / вихід

$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC

Повідомте мене, якщо у вас є додаткові запитання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.