Есть ли способ остановить чтение символов в массиве в структуре после пробела? - PullRequest
0 голосов
/ 16 апреля 2019

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

Fred 23 2.99
Lisa 31 6.99
Sue 27 4.45
Bobby 456  18.844
Ann   7  3.45

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

struct data
{
    char name[25];
    int iNum;
    float fNum;
};

int main(int argc, char *argv[])
{
    struct data mov;
    FILE *fp;

    fp = fopen(argv[1], "r");

    fread(&mov, sizeof(struct data), 1, fp);
    printf(" name: %s\n int: %d\n float: %f\n", mov.name, mov.iNum, mov.fNum);

    return 0;
}

Проблема, с которой я столкнулся, заключается в том, что fread будет считывать первые 25 символов в массив, а не останавливаться на первом пробеле, поэтому он выдает следующие данные:

 name: Fred 23 2.99
Lisa 31 6.99
Sue 27 4.4
 int: 926031973
 float: 0.000000

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

 name: Fred
 int: 23
 float: 2.99000

Из того, что я прочитал, я полагаю, что именно так должен работать fread, и я уверен, что есть более эффективный способ решения этой проблемы, но назначение требует, чтобы мы использовали fread и массив из 25 символов в нашем структура. Как лучше всего это сделать?

1 Ответ

0 голосов
/ 16 апреля 2019

Есть ли способ остановить чтение символов в массиве в структуре после пробела?

Ответ: Да (но не с 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, &currentpos) == -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, &currentpos) == -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, &currentpos) == -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, &currentpos) == -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)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...