Перейти к содержанию

Работа с файлами в C: от открытия до закрытия

ПO для чпу
1 1 8 1
  • Файлы - это то, без чего сложно представить любую серьезную программу. Ведь в реальности нам нужно не просто считать данные с клавиатуры и вывести на экран, а сохранить информацию, чтобы она осталась после завершения программы. Работа с файлами в C может показаться запутанной, но на самом деле это логично и понятно, если разобраться. И особенно это необходимо если мы работаем в промышленности и со станками ЧПУ.

    Работа с файлами строится на трех китах:

    • Открыть файл - сообщить операционной системе, что нам нужен доступ к файлу (для чтения, записи или того и другого)
    • Выполнить операции - читать или записывать данные
    • Закрыть файл - освободить ресурсы и сохранить изменения

    Пропустить третий шаг нельзя - если не закрыть файл, данные могут не записаться, а сам файл останется заблокированным.

    Объект FILE и функция fopen

    Для работы с файлами в C используется структура FILE. Это абстрактный объект, который хранит всю информацию о файловом потоке: указатель на буфер, позицию в файле, индикаторы состояния.

    FILE *file;
    file = fopen("test.txt", "w");
    

    Функция fopen() принимает два параметра:

    • Путь к файлу - может быть абсолютным (C:/projects/data.txt) или относительным (data.txt)
    • Режим доступа - строка, определяющая, что мы собираемся делать с файлом

    Режимы открытия файлов
    Режимы доступа определяют, как мы будем работать с файлом:

    Режим Описание
    “r” Чтение. Файл должен существоватьlearnc+1​
    “w” Запись. Если файл существует — его содержимое удаляетсяlearnc+1​
    “a” Добавление в конец. Файл создаётся, если не существовалlearnc​
    “r+” Чтение и запись. Файл должен существоватьlearnc​
    “w+” Чтение и запись. Старое содержимое теряетсяlearnc​
    “a+” Чтение и добавление в конецlearnc​

    Для бинарных файлов добавляем букву b: "rb", "wb", "ab+" и так далее. В текстовом режиме можно использовать "rt", "wt", хотя по умолчанию и так используется текстовый режим.

    Простой пример: запись и чтение

    Перед тем как приступить к написанию новой программы надеюсь вы уже знаете как делать компиляцию, да и в целом познакомились с базовыми правилами Си. Если нет, то лучше прочитайте и пройдитесь по основам тут. Этих базовых знаний будет не достаточно, материал скорее подготовлен для более опытных программистов чем для новичков, но Вам в любом случае рано или поздно придется столкнуться с файлами.

    Продолжаем, теперь давайте запишем строку в файл, а потом считаем ее обратно:

    #include <stdio.h>
    
    int main() {
        FILE *file;
        char buffer[128];
        
        // Записываем данные
        file = fopen("test.txt", "w");
        fprintf(file, "Hello, World!");
        fclose(file);
        
        // Читаем данные
        file = fopen("test.txt", "r");
        fgets(buffer, 127, file);
        printf("%s", buffer);
        fclose(file);
        
        return 0;
    }
    

    Здесь fprintf() и fgets() работают точно так же, как printf() и gets(), только первым параметром передаётся указатель на файл.

    Обработка ошибок при открытии

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

    file = fopen("data.txt", "r");
    if (file == NULL) {
        printf("Ошибка открытия файла!\n");
        exit(1);
    }
    

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

    inputFile = fopen("input.txt", "r");
    if (inputFile == NULL) {
        printf("Ошибка открытия input.txt\n");
        exit(1);
    }
    
    outputFile = fopen("output.txt", "w");
    if (outputFile == NULL) {
        printf("Ошибка открытия output.txt\n");
        if (inputFile != NULL) {
            fclose(inputFile);  // Закрываем первый файл!
        }
        exit(1);
    }
    

    Буферизация: почему данные не сразу попадают в файл

    Когда мы записываем данные в файл, они сначала попадают в буфер - специальную область памяти. Данные из буфера записываются в файл:

    • Когда буфер заполнен
    • Когда файл закрывается функцией fclose()
    • Когда программа завершается успешно
    • Когда мы явно вызываем fflush(file)

    Пример с принудительной очисткой буфера:

    FILE *file = fopen("test.txt", "w");
    char c;
    
    do {
        c = getch();
        fprintf(file, "%c", c);
        fprintf(stdout, "%c", c);
        fflush(file);  // Данные сразу записываются в файл
    } while(c != 'q');
    
    fclose(file);
    

    Без вызова fflush() данные останутся в буфере до тех пор, пока он не заполнится или файл не закроется.

    Проблема с feof()

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

    // Плохой пример последний символ выведется дважды!
    while (!feof(input)) {
        fscanf(input, "%c", &c);
        fprintf(stdout, "%c", c);
    }
    

    Правильное решение - использовать возвращаемое значение функций чтения. Например, fscanf() возвращает количество успешно прочитанных элементов:

    // Правильный пример
    while (fscanf(input, "%c", &c) == 1) {
        fprintf(stdout, "%c", c);
    }
    

    Или можно использовать EOF для посимвольного чтения:

    int ch;  // int, а не char!
    while ((ch = fgetc(input)) != EOF) {
        printf("%c", ch);
    }
    

    Обратите внимание: переменная должна быть типа int, потому что EOF - это целочисленная константа, а не символ.


    Стандартные потоки: stdin, stdout, stderr

    В любой программе автоматически открываются три стандартных потока:

    • stdin - стандартный ввод (клавиатура)
    • stdout - стандартный вывод (консоль)
    • stderr - поток вывода ошибок (тоже консоль, но можно перенаправить отдельно)

    Это обычные файловые потоки типа FILE*, с которыми можно работать через fprintf() и fscanf():

    int a, b;
    fprintf(stdout, "Введите два числа\n");
    fscanf(stdin, "%d", &a);
    fscanf(stdin, "%d", &b);
    
    if (b == 0) {
        fprintf(stderr, "Ошибка: деление на ноль\n");
    } else {
        fprintf(stdout, "%.3f\n", (float)a / (float)b);
    }
    

    Практические примеры

    Копирование файла посимвольно:

    FILE *origin = fopen("input.txt", "r");
    FILE *copy = fopen("output.txt", "w");
    
    if (origin == NULL || copy == NULL) {
        printf("Ошибка открытия файла\n");
        if (origin) fclose(origin);
        if (copy) fclose(copy);
        exit(1);
    }
    
    int ch;
    while ((ch = fgetc(origin)) != EOF) {
        fputc(ch, copy);
    }
    
    fclose(origin);
    fclose(copy);
    

    Поиск максимального числа в файле:

    FILE *input = fopen("numbers.txt", "r");
    if (input == NULL) {
        printf("Ошибка открытия файла\n");
        exit(1);
    }
    
    int num, maxn = INT_MIN;
    while (fscanf(input, "%d", &num) == 1) {
        if (num > maxn) {
            maxn = num;
        }
    }
    
    printf("Максимальное число: %d\n", maxn);
    fclose(input);
    

    Подсчет строк в файле:

    int countLines(const char *filename) {
        FILE *f = fopen(filename, "r");
        if (f == NULL) return -1;
        
        int lines = 0;
        int ch;
        
        while ((ch = fgetc(f)) != EOF) {
            if (ch == '\n') {
                lines++;
            }
        }
        
        fclose(f);
        return lines;
    }
    

    Резюмируем

    Работа с файлами в C требует дисциплины: нужно всегда проверять результат fopen(), правильно выбирать режим доступа и обязательно закрывать файлы. Понимание буферизации помогает избежать потери данных. А правильное использование функций чтения вместо feof() избавляет от головной боли с дублированием последних элементов.

  • Указатели в C и работа с памятью: Полное руководство

    ПO для чпу
    1
    1 Голоса
    1 Сообщения
    13 Просмотры
    Нет ответов
  • Программирование на C для станков с ЧПУ и промышленного оборудования

    ПO для чпу
    6
    0 Голоса
    6 Сообщения
    34 Просмотры
    kirilljsxK
    Практические советы для начинающих Начинайте с малого Не пытайтесь сразу написать сложную систему управления. Сначала освойте базовые конструкции языка: переменные, циклы, условия, функции. Каждая новая программа должна решать одну небольшую задачу. Изучайте чужой код Открытые проекты на GitHub — отличный источник для обучения. Например, проект GCodeWorkShop показывает, как создаются редакторы программ для станков с ЧПУ. ​ Практикуйтесь регулярно Программирование требует постоянной практики. Пишите код каждый день, даже если это простые упражнения. Решайте задачи на специализированных платформах, пробуйте модифицировать существующие примеры. ​ Объединяйте теорию с практикой Если есть доступ к учебному станку или микроконтроллеру, используйте его для практических экспериментов. Реальное железо дает понимание того, как код превращается в физические действия машин. ​ Общайтесь с сообществом Форумы, Telegram-группы и специализированные ресурсы помогут быстрее разобраться в сложных вопросах. Не стесняйтесь задавать вопросы — сообщество программистов обычно готово помогать начинающим. ​ Дальнейшее развитие После освоения базового C стоит изучить: C++ для объектно-ориентированного программирования и работы с библиотеками MFC, Qt Python для быстрого прототипирования и автоматизации задач Assembler для максимально низкоуровневой работы с процессором Стандарты промышленной автоматизации (IEC 61131-3, OPC UA) Архитектуру микроконтроллеров (ARM Cortex, AVR, PIC) Путь программиста в промышленности требует времени и усилий, но результат того стоит. Вы получаете полный контроль над оборудованием, можете решать уникальные задачи автоматизации и создавать собственные инструменты для производства. ​ Начните с малого, двигайтесь пошагово, и уже через несколько месяцев сможете писать программы для реальных промышленных систем. Удачи в освоении C!
  • МЭК 61131-3: как программировать ПЛК по стандарту

    ПO для чпу мэк
    1
    2
    0 Голоса
    1 Сообщения
    13 Просмотры
    Нет ответов
  • EtherCAT и Modbus TCP: в чём разница и как выбрать для автоматизации

    ПO для чпу
    1
    1
    1 Голоса
    1 Сообщения
    8 Просмотры
    Нет ответов
  • Бесплатные альтернативы дорогому CAM-ПО: FreeCAD, Carbide Create, Estlcam

    ПO для чпу чпу cnc
    1
    0 Голоса
    1 Сообщения
    11 Просмотры
    Нет ответов
  • Как писать базовые УП для линейного перемещения и круговой интерполяции

    ПO для чпу gcode
    1
    0 Голоса
    1 Сообщения
    49 Просмотры
    Нет ответов
  • Как сделать еврозапил на чпу станке

    ПO для чпу
    1
    3 Голоса
    1 Сообщения
    211 Просмотры
    Нет ответов
  • Использование симуляторов для оптимизации процессов металлообработки

    ПO для чпу
    1
    1 Голоса
    1 Сообщения
    132 Просмотры
    Нет ответов