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

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

Для удобства обращения информация в запоминающих устройствах хранится в виде файлов.

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

Каталог (папка, директория) – именованная совокупность байтов на носителе информации, содержащая название подкаталогов и файлов, используется в файловой системе для упрощения организации файлов.

Файловой системой называется функциональная часть операционной системы, обеспечивающая выполнение операций над файлами. Примерами файловых систем являются FAT (FAT – File Allocation Table, таблица размещения файлов), NTFS, UDF (используется на компакт-дисках).

Существуют три основные версии FAT: FAT12, FAT16 и FAT32. Они отличаются разрядностью записей в дисковой структуре, т.е. количеством бит, отведённых для хранения номера кластера. FAT12 применяется в основном для дискет (до 4 кбайт), FAT16 – для дисков малого объёма, FAT32 – для FLASH-накопителей большой емкости (до 32 Гбайт).

Структура файловой системы FAT32

Устройства внешней памяти в системе FAT32 имеют не байтовую, а блочную адресацию. Запись информации в устройство внешней памяти осуществляется блоками или секторами.

Сектор – минимальная адресуемая единица хранения информации на внешних запоминающих устройствах. Как правило, размер сектора фиксирован и составляет 512 байт. Для увеличения адресного пространства устройств внешней памяти сектора объединяют в группы, называемые кластерами.

Кластер – объединение нескольких секторов, которое может рассматриваться как самостоятельная единица, обладающая определёнными свойствами. Основным свойством кластера является его размер, измеряемый в количестве секторов или количестве байт.
Файловая система FAT32 имеет следующую структуру.

Загрузочный сектор512 байт
Информация файловой системы512 байт
Резервные сектора 
Таблица размещения файлов FAT1Число кластеров * 4 (байт)
Таблица размещения файлов FAT2Число кластеров * 4 (байт)
Корневой каталог1 кластер
Массив данных 

Нумерация кластеров, используемых для записи файлов, ведется с 2. Как правило, кластер №2 используется корневым каталогом, а начиная с кластера №3 хранится массив данных. Сектора, используемые для хранения информации, представленной выше корневого каталога, в кластеры не объединяются.

Минимальный размер файла, занимаемый на диске, соответствует 1 кластеру.

Загрузочный сектор начинается следующей информацией:

  • EB 58 90 – безусловный переход и сигнатура;
  • 4D 53 44 4F 53 35 2E 30 MSDOS5.0;
  • 00 02 – количество байт в секторе (обычно 512);
  • 1 байт – количество секторов в кластере;
  • 2 байта – количество резервных секторов.

Кроме того, загрузочный сектор содержит следующую важную информацию:

  • 0x10 (1 байт) – количество таблиц FAT (обычно 2);
  • 0x20 (4 байта) – количество секторов на диске;
  • 0x2С (4 байта) – номер кластера корневого каталога;
  • 0x47 (11 байт) – метка тома;
  • 0x1FE (2 байта) – сигнатура загрузочного сектора (55 AA).

Сектор информации файловой системы содержит:

  • 0x00 (4 байта) – сигнатура (52 52 61 41);
  • 0x1E4 (4 байта) – сигнатура (72 72 41 61);
  • 0x1E8 (4 байта) – количество свободных кластеров, -1 если не известно;
  • 0x1EС (4 байта) – номер последнего записанного кластера;
  • 0x1FE (2 байта) – сигнатура (55 AA).

Таблица FAT содержит информацию о состоянии каждого кластера на диске. Младшие 2 байт таблицы FAT хранят F8 FF FF 0F FF FF FF FF (что соответствует состоянию кластеров 0 и 1, физически отсутствующих). Далее состояние каждого кластера содержит номер кластера, в котором продолжается текущий файл или следующую информацию:

  • 00 00 00 00 – кластер свободен;
  • FF FF FF 0F – конец текущего файла.

Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:

  • 8 байт – имя файла;
  • 3 байта – расширение файла;
  • 1 байт – атрибуты файла;
Атрибуты файла
  • 1 байт – зарезервирован;
  • 1 байт – время создания (миллисекунды) (число от 0 до 199);
  • 2 байта – время создания (с точностью до 2с):
  • 2 байта – дата создания:
  • 2 байта – дата последнего доступа;
  • 2 байта – старшие 2 байта начального кластера;
  • 2 байта – время последней модификации;
  • 2 байта – дата последней модификации;
  • 2 байта – младшие 2 байта начального кластера;
  • 4 байта – размер файла (в байтах).

В случае работы с длинными именами файлов (включая русские имена) кодировка имени файла производится в системе кодировки UTF-8. При этого для кодирования каждого символа отводится 2 байта. При этом имя файла записывается в виде следующей структуры:

  • 1 байт последовательности;
  • 10 байт содержат младшие 5 символов имени файла;
  • 1 байт атрибут;
  • 1 байт резервный;
  • 1 байт – контрольная сумма имени DOS;
  • 12 байт содержат младшие 3 символа имени файла;
  • 2 байта – номер первого кластера;
  • остальные символы длинного имени.

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

Работа с файлами в языке Си

Для программиста открытый файл представляется как последовательность считываемых или записываемых данных. При открытии файла с ним связывается поток ввода-вывода. Выводимая информация записывается в поток, вводимая информация считывается из потока.

Когда поток открывается для ввода-вывода, он связывается со стандартной структурой типа FILE, которая определена в stdio.h. Структура FILE содержит необходимую информацию о файле.

Открытие файла осуществляется с помощью функции fopen(), которая возвращает указатель на структуру типа FILE, который можно использовать для последующих операций с файлом.

 
FILE *fopen(name, type);
  • name – имя открываемого файла (включая путь),
  • type — указатель на строку символов, определяющих способ доступа к файлу:
    • "r" — открыть файл для чтения (файл должен существовать);
    • "w" — открыть пустой файл для записи; если файл существует, то его содержимое теряется;
    • "a" — открыть файл для записи в конец (для добавления); файл создается, если он не существует;
    • "r+" — открыть файл для чтения и записи (файл должен существовать);
    • "w+" — открыть пустой файл для чтения и записи; если файл существует, то его содержимое теряется;
    • "a+" — открыть файл для чтения и дополнения, если файл не существует, то он создаётся.

Возвращаемое значение — указатель на открытый поток. Если обнаружена ошибка, то возвращается значение NULL.

Функция fclose() закрывает поток или потоки, связанные с открытыми при помощи функции fopen() файлами. Закрываемый поток определяется аргументом функции fclose().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
  FILE *fp;
  char name[] = "my.txt";
  if ((fp = fopen(name, "r")) == NULL)
  {
    printf("Не удалось открыть файл");
    getchar();
    return 0;
  }
// открыть файл удалось
...      // требуемые действия над данными
  fclose(fp);
  getchar();
  return 0;
}

Чтение символа из файла:

 
int fgetc(поток);

Аргументом функции является указатель на поток типа FILE. Функция возвращает код считанного символа. Если достигнут конец файла или возникла ошибка, возвращается константа EOF = (-1).

Запись символа в файл:

 
int fputc(символ, поток);

Аргументами функции являются символ и указатель на поток типа FILE. Функция возвращает код считанного символа.

Функции fscanf() и fprintf() аналогичны функциям scanf() и printf(), но работают с файлами данных, и имеют первый аргумент — указатель на файл.

 
fscanf(поток, "ФорматВвода", аргументы);

 
fprintf(поток, "ФорматВывода", аргументы);

Функции fgets() и fputs() предназначены для ввода-вывода строк, они являются аналогами функций gets() и puts() для работы с файлами.

 
fgets(УказательНаСтроку, КоличествоСимволов, поток);

Символы читаются из потока до тех пор, пока не будет прочитан символ новой строки ‘\n’, который включается в строку, или пока не наступит конец потока EOF или не будет прочитано максимальное количество символов. Результат помещается в указатель на строку и заканчивается нуль- символом ‘\0’. Функция возвращает адрес строки.

 
fputs(УказательНаСтроку, поток);

Копирует строку в поток с текущей позиции. Завершающий нуль- символ не копируется.

Пример Ввести число и сохранить его в файле s1.txt. Считать число из файла s1.txt, увеличить его на 3 и сохранить в файле s2.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
  FILE* S1, * S2;
  int x, y;
  system("chcp 1251");
  system("cls");
  printf("Введите число : ");
  scanf("%d", &x);
  S1 = fopen("S1.txt", "w");
  fprintf(S1, "%d", x);
  fclose(S1);
  S1 = fopen("S1.txt", "r");
  S2 = fopen("S2.txt", "w");
  fscanf(S1, "%d", &y);
  y += 3;
  fclose(S1);
  fprintf(S2, "%d\n", y);
  fclose(S2);
  return 0;
}

Результат выполнения – 2 файла

Перемещение указателя внутри файла

Функция fseek() перемещает указатель позиции в потоке. Устанавливает внутренний указатель положения в файле, в новую позицию, которая определяются путем добавления смещения к исходному положению.

 
int fseek( поток, смещение, положение);
  • поток — указатель на объект типа FILE, идентифицируемый поток.
  • смещение — количество байт для смещения, относительно некоторого положения указателя.
  • положение — позиция указателя, относительно которой будет выполняться смещение.

Позиция указателя задается одной из следующих констант, определённых в заголовочном файле stdio.h:

SEEK_SET Начало файла
SEEK_CUR Текущее положение в файле
SEEK_END Конец файла

В случае успеха, функция возвращает нулевое значение.

В противном случае, она возвращает ненулевое значение.

Функция ftell() возвращает значение указателя текущего положения потока. Для бинарных потоков, возвращается значение соответствующее количеству байт от начала файла. В случае успеха, возвращается текущее значение индикатора положения.

Если происходит ошибка, возвращается значение -1.

 
long int ftell(поток);

Пример: смещение позиции в файле

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
  FILE* fp;
  char s[80];
  system("chcp 1251");
  system("cls");
  fp = fopen("S1.txt", "w");
  if (fp == NULL)
  {
    printf("Ошибка открытия файла");
    getchar();
    return 1;
  }
  fputs("Первая строка текста", fp);
  printf("%d", ftell(fp)); // печатаем текущую позицию в файле
  fseek(fp, -4, SEEK_CUR); // смещаемся на 4 байта влево от текущей позиции
  fputs("Вторая строка текста", fp);
  fclose(fp);
  getchar();
  return 0;
}

Результат выполнения

Работа с файлами в C++ описана здесь.

27 комментариев к “Работа с файлами”

  1. Виктор

    Как-то можно передать FILE* в другую функцию? Например:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <stdio.h>
    int main () {
        FILE* fn;
        fr_file_open_read(&(*fn);
    }
    void fr_file_open_read(FILE *fn){
        fopen(fn, "r");
    }

    при таком написании компилятор говорит, что 'fn' may be used uninitialized

    1. Елена

      Так и есть. Указателю ещё не присвоено значение (он не связан ни с каким файлом), и он передается в качестве аргумента

    2. 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      #include "Header.h"

      int main(void) {
       system("chcp 1251>nul");
       FILE* file = fopen("zad1.txt", "r");
       treg* Treg = calloc(0, sizeof(treg));//создаем массив структур, в которую
       //будем помещать данные о треугольниках
       int n = -1, nRows = 0, countStr = 0;//n — количество треугольников (или вершин, если нельзя построить)
       int* rows = calloc(0, 4);//массив, в котором содержатся номера правильных строк
       // 2 4 5
       char* buffer = calloc(100, 1);
       int boolSimvol = 0;
       while (!feof(file)) {
        fgets(buffer, 100, file);
        countStr++;
        for (int i = 0; i < strlen(buffer) — 1; i++)
         //strlen — выводит количество символов в строке
        {
         if ((buffer[i] >= '0' && buffer[i] <= '9') || buffer[i] == '.' 
          || buffer[i] == ' ' || buffer[i] == '-') {
          boolSimvol = 0;
         }
         else {
          boolSimvol = 1;
          break;
         }
        }
        if (boolSimvol == 0) {
         int probel = 0;
         for (int i = 0; i < strlen(buffer); i++) {
          if (buffer[i] == ' ') {
           probel++;
          }
         }
         if (probel == 5) {
          nRows++;
          rows = realloc(rows, nRows * 4);
          rows[nRows — 1] = countStr; //row[0] = 2 — частный случай
         }
        }
        free(buffer);
        buffer = calloc(100, 1);
       }
       printf("Строки, подходящие под шаблон:");
       for (int i = 0; i < nRows; i++) {
        printf(" %d", rows[i]);
       }
       fclose(file);
       file = fopen("zad1.txt", "r");
       countStr = 0; 
       while (!feof(file)) {
        countStr++;//число строки в данный момент
        for (int i = 0; i < nRows; i++) {
         if (countStr == rows[i]) {
          n++;
          Treg = realloc(Treg, n+1 * sizeof(Treg));
          fscanf(file, "%f %f %f %f %f %f", &Treg[n].x1, &Treg[n].y1,
           &Treg[n].x2, &Treg[n].y2, &Treg[n].x3, &Treg[n].y3);
          printf("\n%.2f %.2f %.2f %.2f %.2f %.2f", Treg[n].x1, Treg[n].y1,
           Treg[n].x2, Treg[n].y2, Treg[n].x3, Treg[n].y3);
          break;
         }
        }
        char* buffer = calloc(100, 1);
        fgets(buffer, 100, file);
        free(buffer);
       }
      }

  2. Павел

    Добрый день. Подскажите пожалуйста, возможно ли в С++ читать содержимое только файлов имеющих в своём имени только цифры? Например читать: 56.txt, 78.txt, 99.txt и т.д. И не читать hellou.txt. Если да какие библиотеки и функции нужно использовать? В интернете по обработке имен файлов мало инфы.

    1. Елена Вставская

      Возможно сделать проверку имени файла на соответствие формату. Но само имя файла задаётся пользователем.

  3. Матвей

    Здравствуйте, я сделал посимвольный вывод текстового файла. Как можно реализовать запрет переноса слов в консоли?

    1. Елена Вставская

      Считать количество выведенных символов в строке и длину следующего слова. Если количество символов +длина слова больше 80, то нужно перенести строчку и начать вывод с новой строки. При этом пост вольный вывод не получится — придется использовать буфер (массив) для хранения следующего слова.

  4. Владислав

    Здравствуйте Елена, помогите пожалуйста решить проблему. Программа написанная на C# создаёт отчёт в форме MS Word в папке с файлами программы. Какую функцию надо подключить чтобы была возможность выбрать путь сохранения созданного отчёта?

  5. Артём

    Здравствуйте, такой вопрос:
    У меня есть функция, в которой я несколько раз открываю файл и закрываю. Нужно ли перед каждым открытием файла делать проверку на file == NULL?

      1. Артём

        Оговорился. То есть после каждого открытия файла В ОДНОЙ ФУНКЦИИ, желательно делать проверку на file == NULL?
        Пример:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        void Zadanie
        {
            float k1, k2;
            FILE *zad_5;
            zad_5 = fopen("zadanie5.txt", "r");
            if(zad_5 == NULL)
            {
                printf("Ошибка открытия файла");
                return 1;
            }
            fscanf(zad_5, "%f %f", &k1, &k2);
            fclose(zad_5);

            zad_5 = fopen("zadanie5.txt", "a");
            if(zad_5 == NULL)
            {
                printf("Ошибка открытия файла");
                return 1;
            }
                fprintf(zad_5, "k1^2 = %d, k2^2 = %d", k1*k1, k2*k2);
                }
            }
            fclose(zad_5);
        }

  6. Как можно реализовать возможность перезаписи файла после достижения определенного момента? Т.е. есть файл, в который периодически записываются значения, нужно, чтобы после достижения 120 строк в файле шла перезапись первых записей (т.е. цикличная запись). Т.е. идет 118 строка, 119 строка, 120 строка, затем идет 121 строка, но она перезаписывает 1 строку, строка 122 перезаписывает строку 2. Думаю вы поняли.

    1. Елена Вставская

      Если переместить указатель в начало файла и записать строку, то она запишется поверх первой строки.
      Однако заменятся столько символов, сколько содержит вновь записываемая строка.
      Поэтому способ, который я вижу — это держать буфер строк с ОЗУ, каждый раз менять требуемую строку и перезаписывать все строки в файл.
      Буду рада если кто-то предложит способ лучше.

  7. Александр

    1
    char fgetc(поток);

    — это не правильно EOF не умещается в тип char — сперва надо считывать в переменную типа int, потом проверять нет ли EOF и только потом преобразовывать в char.

  8. Здравствуйте, не подскажете, как написать программу, в которой из существующего файла считать каждый второй (чётный по порядку) символ и вывести на консоль? Массив не использовать.

    1. Елена Вставская

      Считывайте посимвольно и увеличивайте счётчик. Если счётчик четный, выводить в консоль

  9. Сергей

    Здравствуйте. У меня возникла проблема: каким образом можно удалить строку из файла, если я буду искать эту строку по ключевому слову(например, по фамилии)?

    1. Елена Вставская

      Считать все данные из файла, удалить нужную строку и записать в файл заново.

      1. Сергей

        Я имею в виду, есть ли такая функция удаления строки? Если же нет, то как можно самому такую написать?

        1. Елена Вставская

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

  10. Игорь Шапошников

    1
    2
    3
    4
    #include <stdlib.h>

    system("chcp 1251"); //кодировка
    system("cls"); //очистка всего текста что было перед system("cls");
    1. Гудвин

      Да можно проще
      system("@chcp 1251>nul");
      Не помню прописывает ли программа команды прежде, чем выполнить, потому "@". А всё, что команда попытается вывести отправится в чистилище (nul) ;))

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх