Главная страница
Навигация по странице:

  • 7.3. Последовательный доступ к компонентам файла

  • 7.4. Произвольный доступ к компонентам файла

  • В. Ю. Наумов Введение в информатику


    Скачать 2.05 Mb.
    НазваниеВ. Ю. Наумов Введение в информатику
    Анкорosnovy_prog С
    Дата13.02.2022
    Размер2.05 Mb.
    Формат файлаpdf
    Имя файлаosnovy_prog С++.pdf
    ТипДокументы
    #360392
    страница13 из 15
    1   ...   7   8   9   10   11   12   13   14   15
    7.2. Компонентные файлы
    Несмотря на то, что в языке Си файлы не структурированы и представляют из себя непрерывный набор данных, но если с файлом работать как с хранилищем однотипных числовых данных, то он приобретет схожесть с одномерным массивом, т.е. файл также, как и массив, имеет компоненты заданного типа, занимающие определенное место в памяти. Существенным отличием файла от массива является то, что под памятью следует понимать не оперативную память компьютера, а память статическую, расположенную на внешнем носителе.
    Объем информации на внешнем носителе существенно превосходит таковой в оперативной памяти, потому на размер файлов не накладывается ограничение по длине, свойственное массивам. Таким образом, для файла находится место там, где возникает задача обработки информации больших объемов, причем данные не теряются после завершения работы программы или компьютера. Работая сегодня с информацией, мы можем спокойно сделать перерыв и вернуться к ее обработке через несколько дней. В настоящее время общее понятие о файлах у большинства людей,

    211 имеющих опыт работы на компьютере, не вызывает сложностей. Далее рассмотрим более подробно структуру файла и порядок работы с ним.
    Если в массиве за обращение к определенному компоненту отвечал индекс, то в файле принято говорить об указателе (или файловом
    курсоре), который показывает с начала какой позиции будет происходить чтение или запись в файл при ближайшем обращении к нему. Указатель можно представить в условно-графическом виде, как, например, на рис.
    7.2, где он обозначен стрелочкой.
    Следует отметить, что счет позиции указателя начинается с нулевой.
    Это значит, что находясь в начале файла, как, например, в целочисленном
    F1, указатель имеет позицию 0. В файле F2 указатель находится на пятой позиции (5), в F3 – в конце (9), в F4 – на третьей позиции (3).
    Еще одним отличием в графическом представлении файлов являются двойные створки между элементами и на границах. Это обозначение принято для того, чтобы четко видеть, где в данный момент находится курсор. Чтение или запись в файловую ячейку происходит путем
    F1 =
    ‘A’
    ‘a’
    ‘3’
    ‘*’
    ‘B’
    ‘0’
    ‘#’
    0 2
    1 3
    4 5
    6 7
    2.31
    -1.11 2.59
    -6.78
    -4.00
    -0.65 0.00 0
    2 1
    3 4
    5 6
    7
    -9.10 0.00 8
    9
    TRUE
    TRUE
    FALSE
    TRUE
    FALSE
    FALSE
    FALSE
    0 2
    1 3
    4 5
    6 7
    TRUE
    8 2
    -5 8
    1
    -4 0
    3 0
    2 1
    3 4
    5 6
    7
    F2 =
    F3 =
    F4 =
    int
    float
    bool
    char
    Рис. 7.1. Примеры различных файлов (по их содержимому):
    F1 – целочисленный; F2 – логический; F3 – вещественный; F4 – символьный

    212 перемещения курсора вправо. Данное обозначение связано с исторической особенностью хранения файлов на диске.
    Как известно, диск вращается в одну сторону, а считывающая головка при этом меняет свои координаты, приближаясь к центру, либо перемещаясь к периферии.
    Информация на диске записана последовательно концентрическими кольцами. То есть получается, что считывающий элемент находится на месте, в то время как файл проходит мимо него в одном направлении. С некоторой долей условности можно представить указатель в файле в виде считывающей головки диска. При чтении или записи некоторой информации происходит его перемещение в определенном направлении относительно головки. Это направление фиксировано и связано с конструктивной особенностью конкретной модели диска. Очень простая иллюстрация описанного процесса изображена на рис. 7.2.
    Указатель можно переместить на произвольную файловую позицию.
    Делается это при помощи функции fseek. Формат ее записи таков: int fseek(FILE *f, long off, int org); где FILE *f – указатель на файл, long off – позиция смещения, int org – начало отсчета.
    Считывающая головка

    Рис. 7.2. Модель диска и файла на нем

    213
    Смещение задается выражение или переменной и может быть отрицательным, т. е. возможно перемещение как в прямом, так и в обратном направлениях. Начало отсчета задается одной из определенных в файле констант:
    SEEK_SET == 0 – начало файла;
    SEEK_CUR == 1 – текущая позиция;
    SEEK_END == 2 – конец файла.
    Функция возвращает 0, если перемещение в потоке выполнено успешно, иначе возвращает ненулевое значение.
    ПРИМЕР
    //перемещение к началу потока из текущей позиции fseek(f,0L,SEEK_SET);
    //перемещение к концу потока из текущей позиции fseek(f,0L,SEEK_END);
    //перемещение назад на длину переменной а. fseek(f,-(long)sizeof(a),SEEK_SET);
    Следует иметь в виду, что первая файловая позиция имеет нулевой номер, а последняя –номер, равный количеству ячеек в файле; однако при установке указателя в конец файла мы ничего не сможем прочитать
    (поскольку файл закончился), но легко запишем туда нужную нам информацию (если файл открыт в режиме а или а+).
    Часто в задачах требуется узнать, на какой позиции в данный момент находится указатель. Для этого существует функция ftell(). Формат у нее следующий: long ftell(FILE *f); //получает значение указателя текущей позиции в потоке;
    Эта функция через свое имя возвращает значение позиции, на которой в данный момент находится указатель.

    214
    Кроме этой функции, для прямого доступа к файлу используется функция rewind(), позволяющая установить значение указателя на начало потока: void rewind(FILE *f);
    Помимо установки указателя на произвольную позицию, существует еще и задача, собственно, чтения/записи данных в файл. Для этого существует несколько способов.
    Для символьного ввода-вывода используются функции: int fgetc(FILE*fp), где fp – указатель на поток, из которого выполняется считывание.
    Функция возвращает очередной символ в форме int из потока fp. Если символ не может быть прочитан, то возвращается значение EOF. int fputc(int c, FILE*fp), где fp – указатель на поток, в который выполняется запись, c – переменная типа int, в которой содержится записываемый в поток символ. Функция возвращает записанный в поток fp символ в форме int .
    Если символ не может быть записан, то возвращается значение EOF.
    Для построчного ввода-вывода используются следующие функции: char* fgets(char* s,int n,FILE* f), где char*s – адрес, по которому размещаются считанные байты, int n – количество считанных байтов, FILE* f – указатель на файл, из которого производится считывание.
    Прием байтов заканчивается после передачи n-1 байтов или при получении управляющего символа ‘\n’. Управляющий символ тоже передается в принимающую строку. Строка в любом случае заканчивается
    ‘\0’
    . При успешном завершении считывания функция возвращает указатель на прочитанную строку, при неуспешном – 0. int puts(char* s, FILE* f),

    215 где char*s – адрес, из которого берутся записываемые в файл байты,
    FILE* f – указатель на файл, в который производится запись.
    Символ конца строки (‘\0’) в файл не записывается. Функция возвращает EOF, если при записи в файл произошла ошибка, при успешной записи возвращает неотрицательное число.
    ПРИМЕР
    //копирование файла in в файл out int MAXLINE=255;//максимальная длина строки
    FILE *in, //исходный файл
    *out; //принимающий файл char* buf[MAXLINE];//строка для выполнения копирования in=fopen(“f1.txt”,”r”); //открыть исходный файл для чтения out=fopen(“f2.txt”,”w”);//открыть принимающий файл для записи while(fgets(buf,MAXLINE,in)!=0)// прочитать байты из файла in
    // в строку buf fputs(buf,out); //записать байты из строки buf в файл out fclose(in);fclose(out); //закрыть оба файла.
    Для блокового ввода-вывода используются функции: int fread(void*ptr,int size, int n, FILE*f), где void*ptr – указатель на область памяти, в которой размещаются считанные из файла данные, int size – размер одного считываемого элемента, int n – количество считываемых элементов, FILE*f – указатель на файл, из которого производится считывание.
    В случае успешного считывания функция возвращает количество считанных элементов, иначе – EOF. int fwrite(void*ptr,int size, int n, FILE*f), где void*ptr – указатель на область памяти, в которой размещаются записываемые в файл данные, int size – размер одного записываемого элемента, int n – количество записываемых элементов, FILE*f – указатель на файл, в который производится запись.
    В случае успешной записи функция возвращает количество записанных элементов, иначе – EOF.

    216
    Нужно иметь в виду, что как чтение, так и запись ведутся исходя из позиции указателя. После чтения или записи переменной автоматически происходит перемещение указателя на количество обработанных данных вправо. Об этом всегда следует помнить!
    Графически действия функций fwrite и fread можно представить так, как это сделано на рис. 7.3. Здесь указатель изначально стоит на позиции 2, а потому при чтении файла F1 в переменную buf1 происходит копирование ячейки справа от указателя (в ней находится число 8) в ячейку памяти, в которой хранится значение переменной buf1 (теперь и в
    buf1 тоже число 8). И после этого указатель перемещается на следующую позицию вправо. При дальнейшем вызове функции записи fwrite происходит копирование содержимого ячейки памяти, содержащей переменную buf2, в ячейку файла под номером 3. Указатель передвигается на еще одну позицию вправо.
    Теперь рассмотрим алгоритм клавиатурного ввода и вывода файла.
    Начнем с клавиатурного ввода. Для ввода файла можно пользоваться подобно массивам сведения о количестве компонент. Блок-схема типового алгоритма ввода представлена на рис. 7.4. Вариант (а) использует число компонент N, а далее идет последовательный ввод и соответствующая запись элемента. Здесь ввод аналогичен массиву. Однако далеко не всегда
    2
    -1 8
    -4
    -3 0
    9 0
    2 1
    3 4
    5 6
    7 8
    -4
    buf2
    buf1
    F1=
    Рис. 7.1. Иллюстрация чтения компоненты файла F1 в переменную buf1 и записи значения переменной buf2 в файл.

    217 известно точное число компонент наперед. Как правило, файлы содержат достаточно большой объем информации и потому следует прекращать их ввод не исходя из предполагаемого объема, а пользуясь некоторым стоп-
    событием. Вообще, алгоритм, изображенный на рис. 7.4, а является не рекомендуемым и по возможности его следует избегать.
    В варианте (б) рис. 7.4 мы не знаем заранее, сколько всего будет введено компонент и потому можем ввести их произвольное количество.
    Точка остановки ввода (стоп-событие) будет при появлении признака конца файла. В данном случае в качестве такового выбрано число 999. Как только мы вводим число 999, выполняется условие выхода из цикла и i=0; if=fopen(
    “1.txt”,”w”)
    Ввод a (i-я компонента файла)
    fwrite(&a, sizeof(a), 1, f)
    Ввод n (число компонент файла)
    a ≠ 999
    f=fopen(
    “1.txt”,”w”)
    Ввод a (1-я компонента файла)
    fwrite(&a, sizeof(a), 1, f)
    Ввод a (i-я компонента файла)
    Нерекомендуемый ввод файла
    Рекомендуемый ввод файла а)
    б)
    Рис. 7.2. Типовые алгоритмы ввода файлов: а – по известному числу компонент (не рекомендуется к использованию); б – по признаку конца ввода

    218 запись компонент прекращается. Однако в данном случае мы не сможем вписать в файл, собственно, 999.
    Далее рассмотрим последовательный вывод файла. Как и при вводе, можно предложить два вида вывода: по заранее известному числу компонент и по достижению конца файла. При выводе можно использовать тот факт, что количество компонент в файле известно, однако такой подход является нерекомендуемым. Следует производить вывод всех компонент подряд вплоть до достижения конца файла. При этом на каждой итерации производится проверка на достижение указателем конца файла.
    Признак конца файла в данном случае задается при помощи константы EOF, которая возвращается функцией fread при попытке чтения за концом файла, и интерпретируется как логическое значение (TRUE или
    FALSE), в зависимости от того, достигнут конец файла или нет. Название
    EOF является акронимом от английского End Of File, что по-русски i=0; if=fopen(
    “1.txt”,”r”)
    fread(&a, sizeof(a),1,f)
    f=fopen(
    “1.txt”,”r”)
    Нерекомендуемый вывод файла
    Рекомендуемый вывод файла а)
    б)
    fread(&a, sizeof(a),1,f)
    Вывод a (i-я компонента файла)
    Вывод a
    Рис. 7.3. Вывод файла: а) – по известному числу компонент (не рекомендуется к использованию); б) – по признаку конца файла

    219 означает конец файла. Соответствующие блок-схемы вывода представлены на рис. 7.5.
    Поскольку мы не пользуемся функцией установки курсора на произвольную позицию fseek, а только лишь последовательно записываем в файл компоненты, то такой метод работы с файлами называется
    последовательным доступом. Если же используется процедура fseek, то доступ к файлу принято называть произвольным.
    7.3. Последовательный доступ к компонентам файла
    Если обработка элементов файла ведется от начала и до конца последовательно, без использования процедуры принудительного переставления курсора, то такая обработка называется последовательным доступом. fread(&a, sizeof(a),1,f)
    f=fopen(
    “1.txt”,”r+”)
    Выполнение дейтсвий над а fread(&a, sizeof(a),1,f)
    f=fopen(
    “1.txt”,”r+”)
    Условие на а
    Выполнение некоторых действий а)
    б)
    Рис. 7.4. Последовательная безусловная обработка файла (а) и последовательная условная обработка файла (б)

    220
    Если обработке подвергаются все без исключения элементы, то блок- схема будет такой, как на рис. 7.6, а. Если на элемент накладываются некоторые условия, то в тело цикла добавляется дополнительная развилка
    (рис. 7.6, б). Среди ранее рассмотренных алгоритмов последовательным доступом стоит считать ввод и вывод файла.
    Решим несколько типовых задач на последовательный доступ.
    ПРИМЕР
    Найти сумму всех элементов файла. Составим тестовый пример:
    вход:
    -2 3
    8 1
    -10
    -3 0
    9 0
    1 2
    3 4
    5 6
    7 8
    F =
    выход: S = (–2)+3+8+1+0+9+(–10)+(–3) = 6.
    Решение задачи целиком показано на рис. 7.7. а ≠ 999
    Ввод а (1-я компонента файла)
    fwrite(&a, sizeof(a),1,f)
    Ввод a (i-я компонента файла)
    Начало f=fopen(
    “1.txt”,”w”)
    fread(&a, sizeof(a),1,f)
    f=fopen(
    “1.txt”,”r”)
    S=S+a
    Конец
    1 1
    S=0
    вывод S
    fclose(f)
    fclose(f)
    Рис. 7.5. Поиск суммы элементов файла

    221
    Листинг программы.
    #include
    #include int main()
    {
    FILE *f; f=fopen("1.txt","w"); cout<<"\nInput file:"; int i=0,a; cout<<"\nInput first element or 999 for exit"; cin>>a; while (a!=999)
    { fwrite(&a,sizeof(a),1,f); i++; cout<<"\nInput "<>a;
    } fclose(f); f=fopen("1.txt","r"); int s=0; while (fread(&a,sizeof(a),1,f)) s=s+a; cout<<”\ns=”<}
    Следующий пример на последовательную обработку с анализом элементов:
    ПРИМЕР
    Найти среднее арифметическое положительных компонент файла.
    вход:
    -2 3
    8 1
    -10
    -3 0
    9 0
    1 2
    3 4
    5 6
    7 8
    F =
    выход: SrA = (3+8+1+9)/4= 21/4 = 5,25.
    Приведем здесь блок-схему только основного алгоритма без ввода файла, который был расписан ранее. Файл читается от начала до конца и анализируются прочитанные компоненты. Если компоненты оказываются больше нуля, то они добавляются в сумму S; при этом увеличивается счетчик k. Среднее арифметическое SrA существует лишь тогда, когда в

    222 файле есть положительные элементы. Блок-схема описанного процесса показана на рис. 7.8.
    Далее – еще один пример на последовательную обработку.
    ПРИМЕР
    Переписать отрицательные компоненты файла F в файл G. fread(&a, sizeof(a),1,f)
    f=fopen(
    “1.txt”,”r”)
    a>0
    k++
    k = 0
    s = 0
    s = s + a k>0
    SrA = s/k вывод SrA
    ‘В файле нет положительных компонент’
    Рис. 7.6. Среднее арифметическое положительных компонент

    223
    вход:
    -2 3
    8 1
    -10
    -3 0
    9 0
    1 2
    3 4
    5 6
    7 8
    F =
    выход:
    -2
    -10
    -3 0
    1 2
    3
    G =
    Сначала вручную вводится файл F; далее файл проходится в прямом направлении, и встретившиеся отрицательные компоненты копируются последовательно в файл G. Здесь следует обратить внимание на связывание указателя G с местом на диске, занятом под файл F. Решение задачи приведено на рис. 7.9.
    Реализация решения:
    #include
    #include int main()
    {
    FILE *f; f=fopen("1.txt","w"); cout<<"\nInput file:"; int i=0,a; cout<<"\nInput first element or 999 for exit"; cin>>a; while (a!=999)
    { fwrite(&a,sizeof(a),1,f); i++; cout<<"\nInput "<>a;
    } fclose(f); f=fopen("1.txt","r");
    FILE *g; g=fopen("2.txt","w"); while (fread(&a,sizeof(a),1,f))
    { if (a<0) fwrite(&a,sizeof(a),1,g);
    } fclose(f); fclose(g); g=fopen("2.txt","r");

    224 cout<<”\nFile g:”; while (fread(&a,sizeof(a),1,g)) cout<<”\t”<} a ≠ 999
    Ввод a (1-я компонента файла)
    fwrite(&a, sizeof(a),1,f)
    Ввод a (i-я компонента файла)
    Начало f=fopen(
    “1.txt”,”w”)
    fread(&a, sizeof(a),1,g)
    fclose(f)
    Конец fclose(g)
    fread(&a, sizeof(a),1,f)
    a<0
    fwrite(&a, sizeof(a),1,g)
    вывод a f=fopen(
    “1.txt”,”r”)
    g=fopen(
    “2.txt”,”w”)
    fclose(f)
    1 1
    fclose(g)
    g=fopen(
    “2.txt”,”r”)
    Рис. 7.7. Переписывание отрицательных компонент одного файла в другой файл

    225
    7.4. Произвольный доступ к компонентам файла
    Рассмотрим несколько примеров алгоритмов работы с файлами с организацией произвольного доступа. Под произвольным доступом понимается работа с файлом с возможностью произвольного перемещения указателя. Как правило, произвольный доступ обеспечивается функцией
    1   ...   7   8   9   10   11   12   13   14   15


    написать администратору сайта