Ваш код довольно сложно прочитать.Вот почти идентичный код, который (я представляю) значительно более читабелен:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
const char filename[] = "monologue.txt";
FILE *fp;
int i = 0;
char *words = NULL;
char *word = NULL;
int c;
if ((fp = fopen(filename, "r")) == NULL)
{
/*Where monologue txt is a normal file with plain text*/
fprintf(stderr, "Error opening file %s\n", filename);
exit(1);
}
while ((c = fgetc(fp)) != EOF)
{
if (c == '\n')
c = ' ';
words = (char *)realloc(words, ++i * sizeof(char));
words[i-1] = c;
}
word = strtok(words, " ");
while (word != NULL)
{
printf("%s\n", word);
word = strtok(NULL, " ");
}
return(0);
}
Это показывает нам, что вы записываете весь файл в строку, указанную words
, но вы делаете это скореенеэффективно в том, что вы перераспределяете память по одному байту за раз для каждого прочитанного байта.Вы должны стремиться делать что-то гораздо более эффективно, читая большие куски файла в память.Например, вы можете выделить начальный буфер размером 32 КиБ;вы можете читать в этот буфер, используя fread()
;если вы не столкнетесь с EOF, вы можете перераспределить пространство, удвоив доступную вам сумму.(Для тестирования вы начнете с гораздо меньшего блока - может быть, 16 байтов, может быть, даже до 4 байтов; это гарантирует, что вы тестируете код перераспределения памяти, тогда как 32 КиБ, вероятно, редко используют код перераспределения.)
Вы также должны убедиться, что ваша строка завершена нулем;как оно есть, это не так.Вам нужно будет сделать окончательный realloc()
, чтобы освободить место для нулевого терминатора.
Вы можете избежать отображения новых строк во время ввода, поскольку strtok()
может быть предоставлен список символов, по которым нужно разделить символы, так что вы можетеможете добавить новую строку в этот список.
Чтобы создать список слов, вам нужно настроить цикл вокруг strtok()
.Вы можете просто посчитать пробелы и символы новой строки, а затем выделить достаточно указателей, чтобы указать на такое количество слов;Вы можете переоценить, если есть соседние пробелы или переводы строк, но лучше, чем ниже.В качестве альтернативы, вы можете выделить, ради аргумента, 16 указателей.Когда вы обрабатываете первые 16 слов, вы используете эти указатели;когда вам не хватает места, вы удваиваете количество выделенных указателей и используете новый запас, пока он не закончится.Вы можете использовать любой алгоритм, который выделяет значительное количество указателей (что означает «больше чем один» и «увеличивается по мере увеличения уже используемого числа») вместо простого удвоения, но удвоение имеет свои преимущества (в частности, оно простое).
Одно слово предостережения: вам никогда не следует присваивать результат realloc()
переменной, которая является ее первым аргументом:
words = (char *)realloc(words, ++i * sizeof(char)); // Bad!
Проблема в том, что если realloc()
не удастся, вы 'Мы просто уничтожили единственный указатель на ранее выделенную память, так что вы все утекли.Всегда присваивайте новую переменную, проверяйте, что она работает, а затем копируйте результат:
char *new_space = (char *)realloc(words, ++i * sizeof(char));
if (new_space == 0)
{
fprintf(stderr, "Memory allocation failed at size %d\n", i);
exit(1);
}
words = new_space;
Я собрал этот код вчера.Обратите внимание, что он использует функции для выполнения повторяющихся заданий, таких как проверка успешности выделения памяти.Есть место, чтобы улучшить его (всегда есть).Он по-прежнему выполняет символ во время ввода (и, следовательно, сопоставление новой строки), но выделяет все большие и большие фрагменты памяти, так что он не выполняет выделение памяти при каждом чтении символа.Функция err_exit()
является полезным скелетом;Вы можете превратить его в гораздо более сложную систему, но основная идея функции для сообщения об ошибках и выхода (при поведении, подобном fprintf() + exit()
, может значительно упростить программы (и проверка ошибок и создание отчетов важны, но должныбудь проще, когда это возможно).
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void err_exit(const char *format, ...);
static void *emalloc(size_t nbytes);
static void *erealloc(void *old_space, size_t nbytes);
int main(void)
{
const char filename[] = "monologue.txt";
FILE *fp;
size_t i = 0;
size_t len_data = 4;
char *data = emalloc(len_data);
int c;
/* Read data from file */
if ((fp = fopen(filename, "r")) == NULL)
err_exit("Error opening file %s\n", filename);
while ((c = fgetc(fp)) != EOF)
{
if (c == '\n')
c = ' ';
if (i >= len_data)
{
assert(i == len_data);
data = realloc(data, 2 * len_data);
len_data *= 2;
}
data[i++] = c;
}
if (i >= len_data)
{
assert(i == len_data);
data = erealloc(data, len_data + 1);
len_data++;
}
data[i] = '\0';
fclose(fp);
/* Split file into words */
size_t len_wordlist = 16;
size_t num_words = 0;
char **wordlist = emalloc(len_wordlist * sizeof(char *));
char *location = data;
char *word;
for (num_words = 0; (word = strtok(location, " ")) != NULL; num_words++)
{
if (num_words >= len_wordlist)
{
assert(num_words == len_wordlist);
wordlist = erealloc(wordlist, 2 * len_wordlist * sizeof(char *));
len_wordlist *= 2;
}
wordlist[num_words] = word;
location = NULL;
}
/* Print the word list - one per line */
for (i = 0; i < num_words; i++)
printf("%zu: %s\n", i, wordlist[i]);
/* Release allocated space */
free(data);
free(wordlist);
return(0);
}
static void err_exit(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(1);
}
static void *emalloc(size_t nbytes)
{
void *new_space = malloc(nbytes);
if (new_space == 0)
err_exit("Failed to allocate %zu bytes of memory\n", nbytes);
return(new_space);
}
static void *erealloc(void *old_space, size_t nbytes)
{
void *new_space = realloc(old_space, nbytes);
if (new_space == 0)
err_exit("Failed to reallocate %zu bytes of memory\n", nbytes);
return(new_space);
}