посимвольное чтение из файла в C - PullRequest
0 голосов
/ 27 марта 2020

Как читать текст из файла в динамический c массив символов? Я нашел способ подсчитать количество символов в файле и создать массив динамического c, но я не могу понять, как назначить символы для элементов массива?

FILE *text;
char* Str;
int count = 0;
char c;
text = fopen("text.txt", "r");
while(c = (fgetc(text))!= EOF)
{
  count ++;
}
Str = (char*)malloc(count * sizeof(char));

fclose(text);

Ответы [ 3 ]

2 голосов
/ 27 марта 2020

В C нет переносимого, соответствующего стандарту способа заранее знать, как могут считываться байты из потока FILE.

Во-первых, поток может даже не быть доступным - он может быть трубой или клеммой или даже разъемом. В таких потоках, когда вы читаете входные данные, они исчезают, и больше никогда не будут прочитаны. Вы можете вернуть sh одно значение char, но этого недостаточно, чтобы узнать, сколько данных осталось прочитать, или перечитать весь поток.

И даже если поток файл, который вы можете искать, вы не можете использовать fseek() / ftell() в переносимом, строго соответствующем C коде, чтобы узнать, насколько велик файл.

Если это двоичный поток, вы не можете использовать fseek() для поиска в конце файла - это явно неопределенное поведение в соответствии со C стандартом :

... Бинарный поток не требуется Значительно поддерживает fseek вызовы со значением откуда SEEK_END.

Сноска 268 даже говорит :

Установка индикатора положения файла в конец файла, как и в fseek(file, 0, SEEK_END), имеет неопределенное поведение для двоичного потока ...

Таким образом, вы не можете использовать fseek() в двоичном потоке для переноса.

И вы не можете использовать ftell(), чтобы получить количество байтов для текстового потока. По стандарт C снова :

Для текстового потока его индикатор положения файла содержит неопределенную информацию, используемую функцией fseek для возврата индикатора положения файла для потока на свою позицию во время полного вызова; разница между двумя такими возвращаемыми значениями не обязательно является значимым показателем количества написанных или прочитанных символов.

Существуют системы, в которых значение, возвращаемое из ftell(), не имеет ничего общего с числом байтов.

Единственный переносимый, соответствующий способ узнать, сколько байтов вы можете прочитать из потока это на самом деле их читать, и вы не можете полагаться на возможность их чтения снова.

Если вы хотите прочитать весь поток в память, вам необходимо постоянно перераспределять память или использовать некоторые другие динамические * Схема 1090 *.

Это очень неэффективный, но переносимый и строго согласованный способ считывания всего содержимого потока в память (все файлы проверки ошибок и заголовочные файлы опущены для ясности алгоритма и для сохранения вертикальной полосы прокрутки). от появления - это действительно нуждается в проверке ошибок и потребует правильных заголовочных файлов):

// get input stream with `fopen()` or some other manner
FILE *input = ...

size_t count = 0;
char *data = NULL;

for ( ;; )
{
    int c = fgetc( input );
    if ( c == EOF )
    {
        break;
    }

    data = realloc( data, count + 1 );

    data[ count ] = c;

    count++;
}

// optional - terminate the data with a '\0'
// to treat the data as a C-style string
data = realloc( data, count + 1 );
data[ count ] = '\0';
count++;

Это будет работать независимо от того, какой поток.

В системе типа POSIX, такой как Linux, вы можете использовать fileno() и fstat(), чтобы получить размер файла (опять же, все проверки на ошибки и заголовочные файлы опущены d):

char *data = NULL;
FILE *input = ...

int fd = fileno( input );

struct stat sb;

fstat( fd, &sb );

if ( S_ISREG( sb.st_mode ) )
{
    // sb.st_size + 1 for C-style string
    char *data = malloc( sb.st_size + 1 );
    data[ sb.st_size ] = '\0';
}

// now if data is not NULL you can read into the buffer data points to
// if data is NULL, see above code to read char-by-char

// this tries to read the entire stream in one call to fread()
// there are a lot of other ways to do this
size_t totalRead = 0;
while ( totalRead < sb.st_size )
{
    size_t bytesRead = fread( data + totalRead, 1, sb.st_size - totalRead, input );

    totalRead += bytesRead;
}

Вышеуказанное может работать и на Windows. Вы можете получить некоторые предупреждения компилятора или использовать _fileno(), _fstat() и struct _stat вместо этого тоже. *

Вам также может понадобиться определить макрос S_ISREG() для Windows:

#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)

* это _fileno(), _fstat() и struct _stat без подчеркивания гиперссылки. munge.

1 голос
/ 27 марта 2020

Для двоичного файла вы можете использовать fseek и ftell, чтобы узнать размер без чтения файла, выделить память и затем прочитать все:

...
text = fopen("text.txt", "r");
fseek(txt, 0, SEEK_END);
char *ix = Str = malloc(ftell(txt);
while(c = (fgetc(text))!= EOF)
{
  ix++ = c;
}
count = ix - Str;       // get the exact count...
...

Для текстового файла, на система, имеющая многобайтовый конец строки (например, Windows, который использует \r\n), будет выделять больше байтов, чем требуется. Конечно, вы можете сканировать файл дважды, первый раз для размера и второго для фактического чтения символов, но вы также можете просто игнорировать дополнительные байты, или вы можете realloc:

...
count = ix - Str;
Str = realloc(Str, count);
...

Конечно для реальной программы вы должны контролировать возвращаемые значения всех функций ввода-вывода и распределения: fopen, fseek, fteel, malloc и realloc ...

0 голосов
/ 27 марта 2020

Чтобы просто сделать то, что вы просили, вам нужно будет снова прочитать весь файл:

...
// go back to the beginning
fseek(text, 0L, SEEK_SET);
// read
ssize_t readsize = fread(Str, sizeof(char), count, text);
if(readsize != count) {
  printf("woops - something bad happened\n");
}

// do stuff with it
// ...

fclose(text);

Но ваша строка не завершается нулем таким образом. Это доставит вам некоторые неприятности, если вы попытаетесь использовать некоторые распространенные строковые функции, такие как strlen.

. Для правильного завершения строки нулем, вам придется выделить место для одного дополнительного символа и установить для последнего последний значение ' \ 0 ':

...
// allocate count + 1 (for the null terminator) 
Str = (char*)malloc((count + 1) * sizeof(char));    

// go back to the beginning
fseek(text, 0L, SEEK_SET);
// read
ssize_t readsize = fread(Str, sizeof(char), count, text);
if(readsize != count) {
  printf("woops - something bad happened\n");
}
// add null terminator
Str[count] = '\0';

// do stuff with it
// ...

fclose(text);

Теперь, если вы хотите узнать количество символов в файле, не считая их по одному, вы можете получить это число более эффективным способом:

...
text = fopen("text.txt", "r");

// seek to the end of the file
fseek(text, 0L, SEEK_END);
// get your current position in that file
count = ftell(text)

// allocate count + 1 (for the null terminator) 
Str = (char*)malloc((count + 1) * sizeof(char));    
...

Теперь представьте это в более структурированной форме:

// open file
FILE *text = fopen("text.txt", "r");

// seek to the end of the file
fseek(text, 0L, SEEK_END);
// get your current position in that file
ssize_t count = ftell(text)

// allocate count + 1 (for the null terminator) 
char* Str = (char*)malloc((count + 1) * sizeof(char));    

// go back to the beginning
fseek(text, 0L, SEEK_SET);
// read
ssize_t readsize = fread(Str, sizeof(char), count, text);
if(readsize != count) {
  printf("woops - something bad happened\n");
}

fclose(text);

// add null terminator
Str[count] = '\0';

// do stuff with it
// ...

Редактировать:

Как отметил Эндрю Хенле, не каждый поток FILE доступен для поиска, и вы можете даже не полагаться на возможность чтения файла снова (или что файл имеет ту же длину / содержание при повторном чтении). Несмотря на то, что это общепринятый ответ, если вы заранее не знаете, с каким потоком файлов вы имеете дело, его решением, безусловно, будет путь к go.

...