Работа с файлами в C


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

typedef struct Student {    //dynamically allocated structure
    char *first_name;
    char *last_name;
    float grade;
}Student;

Учитывая, Си-это низкоуровневый язык программирования, я хотел в полной мере использовать его возможности динамического выделения памяти. То есть, как имя и фамилия каждого ученика будет динамически выделяемые. Основные структуры данных, которые я использовал, чтобы сохранить студентов-это вектор. В настоящее время приложение имеет следующие особенности:

Файл handling это то, что интересует меня больше всего. Я старался внимательно читать документацию C и сделать своим функциям с меньшим количеством ошибок в плане управления, памяти и ввода-вывода.

Следующие функции предназначены для обработки CSV-файлов:

void Read_From_CSV(const char *file_name, Student *p_destination)
{
    if (!strstr(file_name, ".csv"))
    {
        printf("This method only works for CSV files. \n");
        return;
    }

    FILE *p_file = fopen(file_name, "r");
    if (!p_file)
    {
        perror("File not found or corrupted file. \n");
        exit(EXIT_FAILURE);
    }

    unsigned char buffer_lname[30], buffer_fname[30], buffer[256];
    unsigned int count_students = 0, successfully_read = 0;
    while (fscanf(p_file,"%s",buffer)==1)
    {
        successfully_read = sscanf(buffer, "%[^,],%[^,],%f", buffer_lname, buffer_fname, &p_destination[count_students].grade);
        if (successfully_read != 3)
        {
            printf("Error reading from CSV.\n");
            exit(EXIT_FAILURE);
        }

        p_destination[count_students].last_name = (char*)malloc(strlen(buffer_lname) + 1);
        if (!p_destination[count_students].last_name)
        {
            printf("Couldn't allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        strcpy(p_destination[count_students].last_name, buffer_lname);

        p_destination[count_students].first_name = (char*)malloc(strlen(buffer_fname) + 1);
        if (!p_destination[count_students].first_name)
        {
            printf("Couldn't allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        strcpy(p_destination[count_students].first_name, buffer_fname);

        ++count_students;
    }
    fclose(p_file);
}


void Write_To_CSV(const char *file_name, Student *p_source, const unsigned int number_of_students)
{
    if (!strstr(file_name, ".csv"))
    {
        printf("This method only works for CSV files. \n");
        return;
    }

    FILE *p_file = fopen(file_name, "w");
    if (!p_file)
    {
        perror("An error has occured. \n");
        exit(EXIT_FAILURE);
    }

    unsigned int index = 0;
    int successfully_written = 0;
    while (index < number_of_students)
    {

        successfully_written = fprintf(p_file, "%s,", p_source[index].last_name);
        if (successfully_written != strlen(p_source[index].last_name) + 1)
        {
            printf("An error has occured. \n");
            exit(EXIT_FAILURE);
        }
        successfully_written = fprintf(p_file, "%s,", p_source[index].first_name);
        if (successfully_written != strlen(p_source[index].first_name) + 1)
        {
            printf("An error has occured. \n");
            exit(EXIT_FAILURE);
        }
        successfully_written = fprintf(p_file, "%.2f", p_source[index].grade);
        if (successfully_written != 4 && successfully_written != 5)
        {
            printf("Error occured during grade writing. \n");
            exit(EXIT_FAILURE);
        }
        fprintf(p_file, "\n");
        ++index;
    }
    fclose(p_file);
}

И эти два предназначены для двоичных файлов:

void Read_From_Binary(const char *file_name, Student *p_destination, const unsigned int number_of_students)
{
    if (!strstr(file_name, ".bin"))
    {
        printf("This method only works for binary files. \n");
        return;
    }

    FILE *p_file = fopen(file_name, "rb");
    if (!p_file)
    {
        perror("Error opening file. \n");
        exit(EXIT_FAILURE);
    }

    size_t successfully_read;
    unsigned int width = 0;
    for (unsigned int index = 0; index < number_of_students; ++index)
    {
        successfully_read = fread(&width, sizeof(width), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        p_destination[index].last_name = (char*)malloc(width);
        if (!p_destination[index].last_name)
        {
            printf("Could not allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        successfully_read = fread(p_destination[index].last_name, width, 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_read = fread(&width, sizeof(width), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        p_destination[index].first_name = (char*)malloc(width);
        if (!p_destination[index].first_name)
        {
            printf("Could not allocate memory. \n");
            exit(EXIT_FAILURE);
        }
        successfully_read = fread(p_destination[index].first_name, width, 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_read = fread(&p_destination[index].grade, sizeof(float), 1, p_file);
        if (successfully_read != 1)
        {
            printf("Error reading from the file. \n");
            exit(EXIT_FAILURE);
        }
    }

    fclose(p_file);
}

void Write_To_Binary(const char *file_name, Student *p_source, const unsigned int number_of_students)
{
    if (!strstr(file_name, ".bin"))
    {
        printf("This method only works for binary files. \n");
        return;
    }

    FILE *p_file = fopen(file_name, "wb");
    if (!p_file)
    {
        perror("Error opening the file. \n");
        exit(EXIT_FAILURE);
    }

    size_t successfully_written = 0;
    unsigned int width = 0;
    for (unsigned int index = 0; index < number_of_students; ++index)
    {
        width = strlen(p_source[index].last_name) + 1;

        successfully_written = fwrite(&width, sizeof(width), 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(p_source[index].last_name, width, 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        width = strlen(p_source[index].first_name) + 1;

        successfully_written = fwrite(&width, sizeof(width), 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(p_source[index].first_name, width, 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }

        successfully_written = fwrite(&p_source[index].grade, sizeof(float), 1, p_file);
        if (successfully_written != 1)
        {
            printf("Error writing to the file. \n");
            exit(EXIT_FAILURE);
        }
    }

    fclose(p_file);
}

Обновление

Как я предложил, я добавлю код драйвера, который иллюстрирует использование ранее определенных функций.

Предположим, что у нас есть студенты.в CSV файл, который содержит следующие регистраций:

Вэйн,Брюс,8.50

Кент,Кларк,6.60

Дента,Харви,5.50

Старк,Дэниэл,7

Код драйвера будет импортировать данные из csv-файла. Затем он будет печатать данные на консоль, написать еще один CSV-файл и двоичный файл. Массив студентов очищается и затем мы импортируем данные из двоичного файла, который был ранее создан. Обратите внимание, что структура и функции для обработки файлов уже определены.

#include <stdio.h>
#include <stdlib.h>

void Print_Students(const Student *p_first, const Student *p_last)
{
    unsigned int index = 0;
    for (; p_first < p_last; ++p_first)
    {
        printf("Student %d\n", index);
        printf("Last name: %s \n", p_first->last_name);
        printf("First name: %s \n", p_first->first_name);
        printf("Grade: %.2f \n", p_first->grade);
        ++index;
    }
    printf("\n");
}

void main()
{
    Student *students = (Student*)malloc(4 * sizeof(Student));
    if (!students)
    {
        printf("Couldn't allocate memory.\n");
        exit(EXIT_FAILURE);
    }
    Read_From_CSV("Students.csv", students);
    printf("Reading data from CSV. \n");
    Print_Students(students, students + 4);
    Write_To_CSV("Students1.csv", students, 4);
    Write_To_Binary("Students1.bin", students, 4);
    free(students);
    students = (Student*)malloc(4 * sizeof(Student));
    if (!students)
    {
        printf("Couldn't allocate memory.\n");
        exit(EXIT_FAILURE);
    }
    Read_From_Binary("Students1.bin", students, 4);
    printf("Reading data from binary file. \n");
    Print_Students(students, students + 4);
    free(students);
}

Основные мои вопросы следующие:

  1. Переменные названы правильно?
  2. При работе с файлами, правильно ли я проверить возвращаемое значения из функции, такие как функции fscanf, fwrite, fread операционной и т. д.?
  3. Есть ли лучший способ для извлечения данных из файла CSV?
  4. Это выделение памяти часть сделали правильно?


266
6
задан 10 февраля 2018 в 05:02 Источник Поделиться
Комментарии
3 ответа

Вот это комментарий к первой части, кроме того, я пытаюсь другой формат, все мои комментарии являются встроенными после линии. Это только КШМ чтения/записи часть. Я буду обновлять этот с бинарными в какое-то время.

void Read_From_CSV(const char *file_name, Student *p_destination)
{

if (!strstr(file_name, ".csv"))
{
printf("This method only works for CSV files. \n");
return;
}
// Inconsistent Error Handling:
// Here you print out an error message and return without an errorcode
// the caller won't know nothing was done, in the function below
// the program is just aborted. What is the difference between the two ?

FILE *p_file = fopen(file_name, "r");
if (!p_file)
{
perror("File not found or corrupted file. \n");
exit(EXIT_FAILURE);
}

unsigned char buffer_lname[30], buffer_fname[30], buffer[256];
unsigned int count_students = 0, successfully_read = 0
// if you have it size_t is the type to use for all indices

while (fscanf(p_file,"%s",buffer)==1)
// Assuming memory size:
// if you're line is longer than 256 this will overwrite ...
// fscanf use:
// if there is a space in the line the %s will stop parsing, this of course
// is not so much an issue if you only use this to read data from that you
// created with this program, if you want to read other peoples data this would be a problem.
// Alternative: fread() will read a whole line up to '\n' and also lets you limit
// the amount of characters read
{
successfully_read = sscanf(buffer, "%[^,],%[^,],%f", buffer_lname, buffer_fname, &p_destination
[count_students].grade);
// Assuming memory sizes:
// You're assuming that there p_destination has room for count_students, this usually is not a safe
// assumption to make, the called doesn't know how many students are in the file.
// To fix this you'd want either to tell the parser how many students you can take in p_destination
// or allocate the space dynamically in a list or an array that extends
// You're also assuming that the names are only 30 characters long if they are longer sscanf
// will be writing outside of the buffer to be safe here you'd have to make the name buffers
// as long as the line buffer
// Parsing:
// If this is the only format that you need to parse then this looks fine
// strtok() would be another option to use but that would require more logic

if (successfully_read != 3)
{
printf("Error reading from CSV.\n");
exit(EXIT_FAILURE);
}

p_destination[count_students].last_name = (char*)malloc(strlen(buffer_lname) + 1);
// There is no need to cast the result of malloc to the target type

if (!p_destination[count_students].last_name)
{
printf("Couldn't allocate memory. \n");
// Inexact error message:
// For debugging you will usually want to say what exactly failed
// if you have twenty of these in you're program it's hard to see
// what actually happened
exit(EXIT_FAILURE);
}
strcpy(p_destination[count_students].last_name, buffer_lname);

p_destination[count_students].first_name = (char*)malloc(strlen(buffer_fname) + 1);
if (!p_destination[count_students].first_name)
{
printf("Couldn't allocate memory. \n");
exit(EXIT_FAILURE);
}
strcpy(p_destination[count_students].first_name, buffer_fname);

++count_students;
}
fclose(p_file);
}

void Write_To_CSV(const char *file_name, Student *p_source, const unsigned int number_of_students)
// Naming:
// i'd probably use p_students, source is generic
{
if (!strstr(file_name, ".csv"))
{
printf("This method only works for CSV files. \n");
return;
}

FILE *p_file = fopen(file_name, "w");
if (!p_file)
{
perror("An error has occured. \n");
exit(EXIT_FAILURE);
}

unsigned int index = 0;
int successfully_written = 0;
while (index < number_of_students)
// Safer expression is available:
// if you use a for(size_t index = 0; index < number_of_students; ++index) {}
// it's much harder to forget to write the ++index at the end of the loop
{

successfully_written = fprintf(p_file, "%s,", p_source[index].last_name);
if (successfully_written != strlen(p_source[index].last_name) + 1)
{
printf("An error has occured. \n");
exit(EXIT_FAILURE);
}
successfully_written = fprintf(p_file, "%s,", p_source[index].first_name);
if (successfully_written != strlen(p_source[index].first_name) + 1)
{
printf("An error has occured. \n");
exit(EXIT_FAILURE);
}
successfully_written = fprintf(p_file, "%.2f", p_source[index].grade);
if (successfully_written != 4 && successfully_written != 5)
{
printf("Error occured during grade writing. \n");
exit(EXIT_FAILURE);
}
fprintf(p_file, "\n");
// Very verbose code:
// These lines can be change to use:
// fprintf(p_file, "%s,%s,%.2f\n", p_source[index].last_name, ... )
// Makes this much easier to read
++index;
}
fclose(p_file);
}

2
ответ дан 12 февраля 2018 в 02:02 Источник Поделиться

Выделение и освобождение

При выделении памяти, не привести результат malloc() семьи. Кроме того, мы можем оставить свободу для изменения типа того, что выделено, если мы посчитаем, используя sizeof в результате значение:

Student *students = malloc(sizeof *students * 4);

Мы пишем sizeof выражение сначала, чтобы гарантировать, что весь расчет делается в плане size_t - это не делает никакой разницы здесь, но полезно при выделении для хранения 2-х решеток (malloc(sizeof *array * x * y)).

Когда мы освободим students массив, это важно для бесплатно каждого отдельного члена первый:

free(students);  // leaks every first_name and every last_name

Я рекомендую вам написать Free_Student() функции, чтобы сделать уборку, а также один, чтобы очистить весь массив.


Проверка именем

Я считаю, что это мерзость для данных потребителей для обеспечения именование файлов, но если вы настаиваете на том, его, вы, возможно, захотите, чтобы проверить только конец названия файла, на матч - при условии, что вы хотите data.csv.gz не найденный файл:

char const suffix[] = ".csv";
size_t const suffixlength = sizeof suffix - 1; /* ignoring the NUL */
size_t const filenamelength = strlen(file_name);
if (filenamelength < suffixlength
|| strcmp(suffix, file_name + filenamelength - suffixlength)
{ /* handle bad filename */ }


perror не хочу новой строки

Рассмотрим

    perror("File not found or corrupted file. \n");

Это более информативно, чтобы показать, какой файл ссылается:

    perror(file_name);

Это позволяет избежать дублирования информации (например, "не найден" или "доступ запрещен"), что perror дает нам.


За проверки printf() результаты

В этом коде мы проверяем количество символов, на самом деле написано:

    successfully_written = fprintf(p_file, "%.2f", p_source[index].grade);
if (successfully_written != 4 && successfully_written != 5)

На самом деле, мы просто заинтересованы в том, fprintf() был успешным или нет, и не возражаете, если мы написали 100.00 (6 символов). Так что лучше просто проверить на случай ошибки, когда функция возвращает отрицательное значение:

    if (fprintf(p_file, "%.2f", p_source[index].grade) < 0)

И мы могли бы объединить все печатает в один:

    const Student *const p = p_source+index;
if (fprintf(p_file, "%s,%s,%.2f\n",
p->last_name, p->first_name, p->grade)
< 0)


Думаю о const Student где это уместно

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

void Write_To_CSV(const char *file_name,
Student const *p_source,
const unsigned int number_of_students)


Санировать ваши строки

Что произойдет, если вы студент с , в свое имя? Или "? Если это запрещено, то нужно проверить на записи, и при чтении из файла. Если это allowed1, то вам нужно сделать немного больше форматирования, чтобы создавать и читать допустимых CSV. Это упражнение больше по объему, чем этот один абзац!

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

2
ответ дан 12 февраля 2018 в 02:02 Источник Поделиться

Помимо других хороших ответов:

Разрешить пробелы в именах

while (fscanf(p_file,"%s",buffer)==1) прекращает сканирование с любой белый-пространство, следует за другими персонажами.

// Messes up input
Wayne,Mary Jo,8.50

Лучше использовать fgets(buffer, sizeof buffer, p_file). Это также предотвращает переполнение буфера.

Избежать голый магических чисел

Почему 30, 256? Использовать константы, чтобы позволить для корректного обновления кода и собственной документации.

Быть более терпимыми длинных имен. например, и их строительства должны они содержать неожиданных персонажей, как "'- ,."и т. д.

См. также Ложь программисты верю про имена

// unsigned char buffer_lname[30], buffer_fname[30], buffer[256];
#define NAME_LAST_SIZE 64
#define NAME_FIRST_SIZE 60
#define RECORD_FIRST_SIZE (NAME_LAST_SIZE + 1 + NAME_FIRST_SIZE + 1 + 5 + 2];
unsigned char buffer_lname[NAME_LAST_SIZE];
unsigned char buffer_fname[NAME_FIRST_SIZE];
unsigned char buffer[RECORD_FIRST_SIZE * 2]; // I like 2x max expected size

Соблюдение лимитов

sscanf(buffer, "%[^,],%[^,],%f" позволяет переполнения буфера, ведущее к неопределенному bevaior. Рассмотрим "%29[^,],%29[^,],%f".

strdup()

Общие strdup() или ее эквивалента бы хорошо заменить повторяющийся код. Пример кода

    // p_destination[count_students].last_name = (char*)malloc(strlen(buffer_lname) + 1);
// if (!p_destination[count_students].last_name)
// {
// printf("Couldn't allocate memory. \n");
// exit(EXIT_FAILURE);
// }
// strcpy(p_destination[count_students].last_name, buffer_lname);
p_destination[count_students].last_name = strdup(buffer_lname);
if (p_destination[count_students].last_name == NULL) {
printf("Couldn't allocate memory for last name.\n");
exit(EXIT_FAILURE);
}

Уникальная форма сообщения об ошибках.

А не 2 printf("Couldn't allocate memory. \n");. Лучше сообщения об ошибках в использовании stderr.

    fprintf(stderr, "Couldn't allocate memory. Line: %d\n", __LINE__);
// or
fprintf(stderr, "Couldn't allocate memory last name\n");
fprintf(stderr, "Couldn't allocate memory first name\n");

Создать независимость файла.

ОП код делает файл в зависимости от размера unsigned и этот код в байт. Размер Integer и адресацию варьируется среди платформ.

Вместо того, чтобы выбрать широкий фикчированный тип и преобразовать в фиксированный endian, то пишите. Чтения понадобится обратный процесс.

// unsigned int width = 0;
// width = strlen(p_source[index].last_name) + 1;
// successfully_written = fwrite(&width, sizeof(width), 1, p_file);

uint32_t width = (uint32_t) (strlen(p_source[index].last_name) + 1);
width = htobe32(width);
successfully_written = fwrite(&width, sizeof width, 1, p_file);

Я бы создал вспомогательную функцию, может My_write_uint32(FILE*, uint32_t) чтобы разрешить код повторного использования.
Реф: be64toh()

2
ответ дан 12 февраля 2018 в 05:02 Источник Поделиться