Есть ли способ остановить чтение символов в массиве в структуре после пробела?
Ответ: Да (но не с fread
непосредственно, вам понадобится буфер для выполнения задачи)
Требование, которое вы используете fread
для анализа форматированного текста из входного файла, безусловно, академическое упражнение (хорошее в этом), но нето, что вы обычно делаете.Зачем?Обычно, когда вас интересует чтение строк данных из файла, вы используете строчную функцию ввода, такую как fgets()
или POSIX getline()
.
Вы также можете использовать символьно-ориентированную функцию ввода fgetc()
и просто читать из файла, буферизуя ввод, пока не будет найден '\n'
, делать то, что вам нужно, с помощьюБуфер и повторить.Ваш последний обычный вариант (но не рекомендуется из-за его хрупкости) - использовать функцию отформатированный ввод , такую как fscanf()
, но ее неправильное использование составляет значительный процент вопросов на этом сайте.
Но если для академической задачи вы должны использовать fread()
, то, как упоминалось в комментариях, вы захотите прочитать весь файл в выделенный буфер, а затем проанализировать этот буфер, как если бы вы читали его в строкеза раз из фактического файла.sscanf
будет использоваться при чтении с fgets()
и может использоваться здесь для чтения из буфера, заполненного fread()
.Единственная хитрость - отслеживать, где вы находитесь в буфере для начала каждого чтения, и знать, где остановиться.
С этой схемой, как вы подходите к чтению всего файла в буфер с помощью fread()
?Сначала необходимо получить длину файла, чтобы узнать, сколько места нужно выделить.Это можно сделать, вызвав stat
или fstat
и используя st_size
элемент заполненного struct stat
, содержащего размер файла, или вы используете fseek
, чтобы перейти к концу файла, и использовать ftell()
, чтобысообщить о смещении в байтах от начала.
Простая функция, которая берет открытый указатель FILE*
, сохраняет текущую позицию, перемещает индикатор положения файла в конец, получает размер файла с ftell()
и затем восстанавливает исходное положение индикатора положения файла:
/* get the file size of file pointed to by fp */
long getfilesize (FILE *fp)
{
fpos_t currentpos;
long bytes;
if (fgetpos (fp, ¤tpos) == -1) { /* save current file position */
perror ("fgetpos");
return -1;
}
if (fseek (fp, 0, SEEK_END) == -1) { /* fseek end of file */
perror ("fseek-SEEK_END");
return -1;
}
if ((bytes = ftell (fp)) == -1) { /* get number of bytes */
perror ("ftell-bytes");
return -1;
}
if (fsetpos (fp, ¤tpos) == -1) { /* restore file positon */
perror ("fseek-SEEK_SET");
return -1;
}
return bytes; /* return number of bytes in file */
}
( примечание: над каждым шагом проверяется и -1
возвращается по ошибке, в противном случаеРазмер файла возвращается в случае успеха. Убедитесь, что вы проверили каждый шаг в своей программе и всегда содержали значительную отдачу от своих функций, которая может указывать на успех / неудачу.)
Имея размер файла, все, что вам нужноперед вызовом fread()
необходимо выделить достаточно большой блок памяти для хранения содержимого файла и присвоить начальный адрес этому блоку памятина указатель, который можно использовать с fread()
.Например:
long bytes; /* size of file in bytes */
char *filebuf, *p; /* buffer for file and pointer to it */
...
if ((bytes = getfilesize (fp)) == -1) /* get file size in bytes */
return 1;
if (!(filebuf = malloc (bytes + 1))) { /* allocate/validate storage */
perror ("malloc-filebuf");
return 1;
}
(мы поговорим о + 1
позже)
Теперь у вас достаточно места для хранения вашего файла и адрес для хранения назначен указателю filebuf
, вы можете вызвать fread()
и прочитать весь файл в этот блок памяти с помощью:
/* read entire file into allocated memory */
if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) {
perror ("fread-filebuf");
return 1;
}
Теперь весь ваш файл хранится в блоке памяти, указанном filebuf
.Как вы разбираете данные построчно в вашу структуру (или на самом деле массив struct, чтобы каждая запись сохранялась в отдельной структуре)?Это на самом деле довольно легко.Вы просто читаете из буфера и отслеживаете количество символов, используемых для чтения, до тех пор, пока не будет найдено '\n'
, анализируя информацию в этой строке в элементе структуры массива, добавьте смещение к вашему указателю, чтобы подготовиться кЗатем прочитайте и увеличьте индекс вашего массива struct, чтобы учесть только что заполненную структуру.По сути, вы используете sscanf
так же, как если бы вы читали строку из файла с fgets()
, но вы вручную отслеживаете смещение в буфере для следующего вызова sscanf
, например
#define NDATA 16 /* if you need a constant, #define one (or more) */
#define MAXC 25
struct data { /* your struct with fixed array of 25-bytes for name */
char name[MAXC];
int iNum;
float fNum;
};
...
struct data arr[NDATA] = {{ .name = "" }}; /* array of struct data */
int used; /* no. chars used by sscanf */
size_t ndx = 0, offset = 0; /* array index, and pointer offset */
...
filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
p = filebuf; /* set pointer to filebuf */
while (ndx < NDATA && /* while space in array */
sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */
arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) {
offset += used; /* update offset with used chars */
ndx++; /* increment array index */
}
Вот и все.Вы можете free (filebuf);
теперь, когда вы закончили с ним, и все значения теперь сохранены в вашем массиве struct arr
.
Есть одна важная строка кода, о которой мы не говорили - и я сказал вам, что мы вернемся к ней позже. Это также то, что вы обычно не делаете, но обязательно, когда вы собираетесь обрабатывать буфер как текст с помощью sscanf
, функции, обычно используемой для обработки строк. Как вы убедитесь, что sscanf
знает, что перестали читать, и не продолжит чтение за пределами filebuf
?
filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
Вот где + 1
на выделенном размере вступает в игру. Вы обычно не завершаете буфер - в этом нет необходимости. Тем не менее, если вы хотите обработать содержимое буфера с помощью функций, обычно используемых для обработки строк - тогда вам нужно. В противном случае, sscanf
продолжит чтение после последнего '\n'
из буфера в памяти, к которой вы не сможете получить доступ, пока не найдете случайное значение 0
где-нибудь в куче. (с возможностью заполнения дополнительных дополнительных структур мусором, если они удовлетворяют форматной строке )
В целом, вы можете сделать:
#include <stdio.h>
#include <stdlib.h>
#define NDATA 16 /* if you need a constant, #define one (or more) */
#define MAXC 25
struct data { /* your struct with fixed array of 25-bytes for name */
char name[MAXC];
int iNum;
float fNum;
};
long getfilesize (FILE *fp); /* function prototype for funciton below */
int main (int argc, char **argv) {
struct data arr[NDATA] = {{ .name = "" }}; /* array of struct data */
int used; /* no. chars used by sscanf */
long bytes; /* size of file in bytes */
char *filebuf, *p; /* buffer for file and pointer to it */
size_t ndx = 0, offset = 0; /* array index, and pointer offset */
FILE *fp; /* file pointer */
if (argc < 2) { /* validate at least 1-arg given for filename */
fprintf (stderr, "error: insufficient arguments\n"
"usage: %s <filename>\n", argv[0]);
return 1;
}
/* open file / validate file open for reading */
if (!(fp = fopen (argv[1], "rb"))) {
perror ("file open failed");
return 1;
}
if ((bytes = getfilesize (fp)) == -1) /* get file size in bytes */
return 1;
if (!(filebuf = malloc (bytes + 1))) { /* allocate/validate storage */
perror ("malloc-filebuf");
return 1;
}
/* read entire file into allocated memory */
if (fread (filebuf, sizeof *filebuf, bytes, fp) != (size_t)bytes) {
perror ("fread-filebuf");
return 1;
}
fclose (fp); /* close file, read done */
filebuf[bytes] = 0; /* trick - nul-terminate buffer for sscanf use */
p = filebuf; /* set pointer to filebuf */
while (ndx < NDATA && /* while space in array */
sscanf (p + offset, "%24s %d %f%n", /* parse values into struct */
arr[ndx].name, &arr[ndx].iNum, &arr[ndx].fNum, &used) == 3) {
offset += used; /* update offset with used chars */
ndx++; /* increment array index */
}
free (filebuf); /* free allocated memory, values stored in array */
for (size_t i = 0; i < ndx; i++) /* output stored values */
printf ("%-24s %4d %7.3f\n", arr[i].name, arr[i].iNum, arr[i].fNum);
return 0;
}
/* get the file size of file pointed to by fp */
long getfilesize (FILE *fp)
{
fpos_t currentpos;
long bytes;
if (fgetpos (fp, ¤tpos) == -1) { /* save current file position */
perror ("fgetpos");
return -1;
}
if (fseek (fp, 0, SEEK_END) == -1) { /* fseek end of file */
perror ("fseek-SEEK_END");
return -1;
}
if ((bytes = ftell (fp)) == -1) { /* get number of bytes */
perror ("ftell-bytes");
return -1;
}
if (fsetpos (fp, ¤tpos) == -1) { /* restore file positon */
perror ("fseek-SEEK_SET");
return -1;
}
return bytes; /* return number of bytes in file */
}
( примечание: приблизительно 1/2 строки кода посвящено проверке каждого шага . Это нормально и важно, чтобы вы не вызовите неопределенное поведение , слепо продолжая пересылку в вашем коде после сбоя, который не позволяет вам обрабатывать действительные данные.)
Пример использования / Вывод
После этого ваша программа будет завершена, и вы сможете анализировать данные из буфера, заполненного fread()
, останавливаясь во все соответствующие моменты времени после пробела.
$ ./bin/freadinumfnum dat/inumfnum.txt
Fred 23 2.990
Lisa 31 6.990
Sue 27 4.450
Bobby 456 18.844
Ann 7 3.450
Использование памяти / проверка ошибок
В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, , чтобы подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
- нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/freadinumfnum dat/inumfnum.txt
==5642== Memcheck, a memory error detector
==5642== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5642== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==5642== Command: ./bin/freadinumfnum dat/inumfnum.txt
==5642==
Fred 23 2.990
Lisa 31 6.990
Sue 27 4.450
Bobby 456 18.844
Ann 7 3.450
==5642==
==5642== HEAP SUMMARY:
==5642== in use at exit: 0 bytes in 0 blocks
==5642== total heap usage: 2 allocs, 2 frees, 623 bytes allocated
==5642==
==5642== All heap blocks were freed -- no leaks are possible
==5642==
==5642== For counts of detected and suppressed errors, rerun with: -v
==5642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.