Чтобы разделить строку с неизвестным количеством слов и сделать ее доступной взамен функции, потребуется функция, которая возвращает pointer-to-pointer-to-char . Это позволяет использовать настоящий динамический подход, при котором вы выделяете некоторое начальное количество указателей (скажем, 2, 4, 8
и т. Д.), Делая один проход по вашей строке, используя strtok
, отслеживая количество используемых указателей, выделяя хранилище для каждого токена ( слово), и когда вы используете количество указателей, равное выделенному количеству, вы просто realloc
сохраняете дополнительные указатели и продолжаете.
Краткий пример реализации функции splitstring()
, которая может выглядеть примерно так:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NPTR 8 /* initial number of pointers to allocate */
#define MAXD 32 /* maximum no chars for delimiter */
#define MAXC 1024 /* maximum no chars for user input */
char **splitstring (const char *str, const char *delim, size_t *nwords)
{
size_t nptr = NPTR, /* initial pointers */
slen = strlen (str); /* length of str */
char **strings = malloc (nptr * sizeof *strings), /* alloc pointers */
*cpy = malloc (slen + 1), /* alloc for copy of str */
*p = cpy; /* pointer to cpy */
*nwords = 0; /* zero nwords */
if (!strings) { /* validate allocation of strings */
perror ("malloc-strings");
free (cpy);
return NULL;
}
if (!cpy) { /* validate allocation of cpy */
perror ("malloc-cpy");
free (strings);
return NULL;
}
memcpy (cpy, str, slen + 1); /* copy str to cpy */
/* split cpy into tokens */
for (p = strtok (p, delim); p; p = strtok (NULL, delim)) {
size_t len; /* length of token */
if (*nwords == nptr) { /* all pointers used/realloc needed? */
void *tmp = realloc (strings, 2 * nptr * sizeof *strings);
if (!tmp) { /* validate reallocation */
perror ("realloc-strings");
if (*nwords) /* if words stored, return strings */
return strings;
else { /* no words, free pointers, return NULL */
free (strings);
return NULL;
}
}
strings = tmp; /* assign new block to strings */
nptr *= 2; /* update number of allocate pointers */
}
len = strlen (p); /* get token length */
strings[*nwords] = malloc (len + 1); /* allocate storage */
if (!strings[*nwords]) { /* validate allocation */
perror ("malloc-strings[*nwords]");
break;
}
memcpy (strings[(*nwords)++], p, len + 1); /* copy to strings */
}
free (cpy); /* free storage of cpy of str */
if (*nwords) /* if words found */
return strings;
free (strings); /* no strings found, free pointers */
return NULL;
}
int main (void) {
char **strings = NULL,
string[MAXC],
delim[MAXD];
size_t nwords = 0;
fputs ("enter string : ", stdout);
if (!fgets (string, MAXC, stdin)) {
fputs ("(user canceled input)\n", stderr);
return 1;
}
fputs ("enter delimiters: ", stdout);
if (!fgets (delim, MAXD, stdin)) {
fputs ("(user canceled input)\n", stderr);
return 1;
}
if ((strings = splitstring (string, delim, &nwords))) {
for (size_t i = 0; i < nwords; i++) {
printf (" word[%2zu]: %s\n", i, strings[i]);
free (strings[i]);
}
free (strings);
}
else
fputs ("error: no delimiter found\n", stderr);
}
( примечание: счетчик слов nwords
передается как указатель на функцию splitstring()
, чтобы количество слов, которые были обновлены в функции и было доступно обратно в вызывающей функции, при возврате указатель на указатель на символ из самой функции)
Пример использования / Вывод
$ ./bin/stringsplitdelim
enter string : my dog has fleas and my cat has none and snakes don't have fleas
enter delimiters:
word[ 0]: my
word[ 1]: dog
word[ 2]: has
word[ 3]: fleas
word[ 4]: and
word[ 5]: my
word[ 6]: cat
word[ 7]: has
word[ 8]: none
word[ 9]: and
word[10]: snakes
word[11]: don't
word[12]: have
word[13]: fleas
( примечание: a ' '
(пробел) было введено в качестве разделителя выше, в результате чего delim
содержит " \n"
(именно то, что вы хотите) благодаря использованию строкового ввода функция fgets
для пользовательского ввода)
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/stringsplitdelim
==12635== Memcheck, a memory error detector
==12635== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==12635== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==12635== Command: ./bin/stringsplitdelim
==12635==
enter string : my dog has fleas and my cat has none and snakes don't have fleas
enter delimiters:
word[ 0]: my
word[ 1]: dog
word[ 2]: has
word[ 3]: fleas
word[ 4]: and
word[ 5]: my
word[ 6]: cat
word[ 7]: has
word[ 8]: none
word[ 9]: and
word[10]: snakes
word[11]: don't
word[12]: have
word[13]: fleas
==12635==
==12635== HEAP SUMMARY:
==12635== in use at exit: 0 bytes in 0 blocks
==12635== total heap usage: 17 allocs, 17 frees, 323 bytes allocated
==12635==
==12635== All heap blocks were freed -- no leaks are possible
==12635==
==12635== For counts of detected and suppressed errors, rerun with: -v
==12635== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.