Главная страница

Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


Скачать 1.57 Mb.
НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
АнкорКурс на Си
Дата18.02.2023
Размер1.57 Mb.
Формат файлаdocx
Имя файлаПодбельский. Курс программирования на Си.docx
ТипУчебник
#943863
страница25 из 42
1   ...   21   22   23   24   25   26   27   28   ...   42

fgetc( ), getc( ) — ввод (чтение) одного символа из файла;

  • fputc( ), putc( ) - запись одного символа в файл;

  • fprintf( ) - форматированный вывод в файл;

  • fscanf( ) - форматированный ввод (чтение) из файла;

  • fgets( ) - ввод (чтение) строки из файла;

  • fputs( ) - запись строки в файл.

    Двоичный (бинарный) режим обмена с файлами. Двоичный ре­жим обмена организуется с помощью функций getc( ) и putc( ), обращение к которым имеет следующий формат:

    с = getc(fp);

    putc(c, fp);

    где fp - указатель на поток; c - переменная типа int для приема очередного символа из файла или для записи ее значения в файл.

    Прототипы функции:

    int getc ( FILE *stream );

    int putc ( int c, FILE *stream );

    В качестве примера использования функций getc( ) и putc( ) рас­смотрим программы ввода данных в файл с клавиатуры и программу вывода их на экран дисплея из файла.

    Программа ввода читает символы с клавиатуры и записывает их в файл. Пусть признаком завершения ввода служит поступивший от клавиатуры символ '#'. Имя файла запрашивается у пользователя. Если при вводе последовательности символов была нажата клави­ша , служащая разделителем строк при вводе c клавиату­ры, то в файл записываются управляющие коды «Возврат каретки» (CR - значение 13) и «Перевод строки» (LF - значение 10). Код CR в дальнейшем при выводе вызывает перевод курсора в начало стро­ки. Код LF служит для перевода курсора на новую строку дисплея. Значения этих кодов в тексте программы обозначены соответствен­но идентификаторами CR и LF, то есть CR и LF - именованные константы. Запись управляющих кодов CR и LF в файл позволяет при последующем выводе файла отделить строки друг от друга.

    В приводимых ниже программах используются уже рассмотрен­ные выше функции getchar( ), putchar( ) для посимвольного обмена со стандартными потоками stdin, stdout.

    /* Программа ввода символов */

    #include

    int main()

    {

    FILE *fp; /* Указатель на поток */ char c;

    /* Восьмеричный код "Возврат каретки": */ const char CR='\015';

    /* Восьмеричный код "Перевод строки": */ const char LF = '\012';

    char fname[20]; /* Массив для имени файла */

    /* Запрос имени файла: */

    puts("BBegume имя файла: \n");

    gets(fname);

    /* Открыть файл для записи: */

    if ((fp = fopen(fname,"w")) == NULL)

    {

    perror(fname);

    return 1;

    }

    /* Цикл ввода и записи в файл символов: */ while ((c = getchar()) != '#')

    {

    if (c == '\n')

    {

    putc(CR, fp);

    putc(LF, fp);

    }

    else putc(c, fp);

    }

    /* Цикл ввода завершен; закрыть файл: */ fclose(fp);

    return 0;

    }

    Следующая программа читает поток символов из ранее созданно­го файла и выводит его на экран дисплея:

    /* Программа вывода символьного файла на экран дисплея */ #include int main() {

    FILE *fp; /* Указатель на поток */ char c;

    char fname[20]; /* Массив для имени файла */

    /* Запрос имени файла: */ puts("BBegume имя файла: \n "); gets(fname);

    /* Открыть файл для чтения: */

    if ((fp = fopen(fname,"r")) == NULL) {

    perror(fname); return 1;

    }

    /* Цикл чтения из файла и вывода символов на экран: */

    while ((c = getc(fp)) != EOF) putchar(c);

    fclose(fp); /* Закрыть файл */ return 0;

    }

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

    /* Усовершенствованная программа вывода символьного файла на экран дисплея по кадрам */

    #include

    int main()

    {

    FILE *fp; /* Указатель на поток) */

    char c;

    /* Восьмеричный код "Перевод строки": */

    const char LF='\012';

    int MAX=10; /* Размер кадра */

    int nline; /* Счетчик строк */

    char fname[10]; /* Массив для имени файла */

    /* Запрос имени файла: */

    puts("BBegume имя файла \n");

    gets(fname);

    /* Открыть файл для чтения: */

    if ((fp = fopen(fname,»r»)) = = NULL)

    {

    perror(fname);

    return 1;

    }

    while (1)

    { /* Цикл вывода на экран */

    nline = 1;

    while (nline
    c = getc(fp);

    if (c == EOF)

    {

    fclose(fp);

    return 0;

    }

    if (c == LF) nline++;

    putchar(c);

    } /* Конец цикла вывода кадра */

    getchar(); /* Задержка вывода до нажатия любой клавиши */

    } /* Конец цикла вывода */

    } /* Конец программы */

    В этой программе после вывода очередного кадра из MAX строк для перехода к следующему кадру необходимо нажать любую кла­вишу.

    Используя двоичный обмен с файлами, можно сохранять на дис­ке информацию, которую нельзя непосредственно вывести на экран дисплея (целые и вещественные числа во внутреннем представ­лении).

    Строковый обмен с файлами. Для работы с текстовыми файлами, кроме перечисленных выше, удобно использовать функции fgets( ) и fputs( ). Прототипы этих функций в файле stdio.h имеют следую­щий вид:

    int fputs(const char *s, FILE *stream);

    char * fgets(char *s, int n, FILE *stream);

    Функция fputs( ) записывает строку (на которую указывает s) в файл, определенный указателем stream на поток, и возвращает не­отрицательное целое. При ошибках функция возвращает EOF.

    Функция fgets( ) читает из определенного указателем stream фай­ла не более (n-1) символов и записывает их в строку, на которую указывает s. Функция прекращает чтение, как только прочтет (n-1) символов или встретит символ новой строки '\n', который перено­сится в строку s. Дополнительно в конец каждой строки записыва­ется признак окончания строки '\0'. В случае успешного завершения функция возвращает указатель s. При ошибке или при достижении конца файла, при условии, что из файла не прочитан ни один сим­вол, возвращается значение NULL. В этом случае содержимое мас­сива, который адресуется указателем s, остается без изменений.

    Проиллюстрируем возможности указанных функций на програм­ме, которая переписывает текст из одного файла в другой. На этом же примере еще раз проиллюстрируем особенность языка Си - возможность передачи информации в выполняемую программу из командной строки (см. §5.8).

    В стандарте языка Си определено, что любая программа на языке Си при запуске на выполнение получает от операционной системы информацию в виде значений двух аргументов - argc (сокраще­ние от argument count - счетчик аргументов) и argv (сокращение от argument vector). Первый из них определяет количество строк, передаваемых в виде массива указателей argv. По принятому со­глашению, argv[0] - это всегда указатель на строку, содержащую полное имя файла с выполняемой программой. Остальные элементы массива argv[1],...,argv[argc-1] содержат ссылки на параметры, запи­санные через пробелы в командной строке после имени программы. (Третий параметр char * envp[ ] мы уже рассмотрели в §5.8, но он используется весьма редко.)

    Предположим, что имя выполняемой программы copyfile.exe, и мы хотим с ее помощью переписать содержимое файла f1.dat в файл f2.txt. Вызов программы из командной строки имеет вид (> - «при­глашение» от операционной системы):

    >copyfile.exe f1.dat f2.txt

    Текст программы может быть таким:

    #include

    main(int argc, char *argv[ ])

    {

    char cc[256];/* Массив для обмена с файлами */

    FILE *f1, *f2; /* Указатели на потоки */

    if (argc != 3) /* Проверка командной строки */ {

    printf("\n Формат вызова программы: ");

    printf("\n copyfile.exe"

    printf("\n файл-источник файл-приемник"); return 1;

    }

    if ((f1 = fopen(argv[1], "r")) == NULL)

    /* Открытие входного файла */ {

    perror(argv[1]);

    return 1;

    }

    if ((f2 = fopen(argv[2], "w")) == NULL)

    /* Открытие выходного файла */ {

    perror(argv[2]);

    return 1;

    }

    while (fgets(cc, 256, f1) != NULL) fputs(cc, f2);

    fclose(f1);

    fclose(f2); return 0;

    }

    Обратите внимание, что значение argc явно не задается, а опре­деляется автоматически. Так как argv[0] определено всегда, то зна­чение argc не может быть меньше 1. При отсутствии в командной строке двух аргументов (имен файлов ввода-вывода) значение argc оказывается не равным трем, и поэтому на экран выводится пояс­няющее сообщение:

    Формат вызова программы: copyfile.exe файл-источник файл-приемник

    Если имя входного файла указано неудачно или нет места на дис­ке для создания выходного файла, то выдаются соответствующие сообщения:

    Ошибка при открытии файла f1.dat

    или

    Ошибка при открытии файла f2.txt

    Режим форматного обмена с файлами. В некоторых случаях ин­формацию удобно записывать в файл в виде, пригодном для непо­средственного (без преобразования) отображения на экран дисплея, то есть в символьном виде. Например, для сохранения результатов работы программы, чтобы затем распечатать их на бумагу (полу­чить «твердую» копию), или когда необходимо провести трассиров­ку программы - вывести значения некоторых переменных во время выполнения программы для их последующего анализа. В этом слу­чае можно воспользоваться функциями форматного ввода (вывода) в файл fprintf( ) и fscanf( ), которые имеют следующие прототипы:

    int fprintf ( указатель_на_поток, форматная-строка, список-переменных);

    int fscanf ( указатель_на_поток, форматная_строка, список_адресов_переменных);

    Как и в функциях printf( ) и scanf( ), форматная строка может быть определена вне вызова функции, а в качестве аргумента в этом случае будет указатель на нее.

    От рассмотренных выше функций printf( ), scanf( ) для формат­ного обмена с дисплеем и клавиатурой функции fprintf( ) и fscanf( ) отличаются лишь тем, что в качестве первого параметра в них необ­ходимо задавать указатель на поток, с которым производится обмен. В качестве примера приведем программу, создающую файл int.dat и записывающую в него символьные изображения чисел от 1 до 10 и их квадратов [9]:

    #include

    int main()

    {

    FILE *fp; /* Указатель на поток */

    int n;

    if ((fp = fopen("int.dat","w")) == NULL)

    {

    perror("int.dat");

    return 1;

    }

    for (n=1; n<11; n++)

    fprintf(fp, "%d %d\n", n, n*n);

    fclose(fp);

    return 0;

    }

    В результате работы этой программы в файле int.dat формируется точно такая же «картинка», как и в том случае, если бы мы восполь­зовались вместо функции fprintf( ) функцией printf( ) для вывода результатов непосредственно на экран дисплея. В этом легко убе­диться, воспользовавшись соответствующей обслуживающей про­граммой операционной системы для вывода файла int.dat на экран дисплея. Несложно и самим написать такую программу, используя для форматного чтения данных из файла функцию fscanf( ):

    #include

    int main()

    {

    FILE *fp; /* Указатель на поток */

    int n, nn, i;

    if ((fp = fopen("int.dat","r")) == NULL) {

    perror("int.dat");

    return 1;

    }

    for (i=1; i<11; i++)

    {

    fscanf(fp, "%d %d", &n, &nn);

  • printf(" %d %d \n",n, nn);

    }

    fclose(fp); return 0;

    }

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

    Операция чтения (или записи) для потока всегда производится, начиная с текущей позиции в потоке. Начальная позиция чтения/ записи в потоке устанавливается при открытии потока и может со­ответствовать начальному или конечному байту потока в зависимо­сти от режима открытия потока. При открытии потока в режимах «r» и «w» указатель текущей позиции чтения/записи в потоке уста­навливается на начальный байт потока, а при открытии в режиме «а» - в конец файла (за конечным байтом). При выполнении каж­дой операции ввода-вывода указатель текущей позиции в потоке перемещается на новую текущую позицию в соответствии с числом прочитанных (записанных) байтов.

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

    В библиотеку языка Си включена функция fseek( ) для переме­щения (установки) указателя текущей позиции в потоке на нужный байт потока (файла). Она имеет следующий прототип:

    int fseek (указатель_на_поток, смещение, начало_отсчета);

    Смещение задается переменной или выражением типа long и мо­жет быть отрицательным, то есть возможно перемещение по файлу в прямом и обратном направлениях. Начало отсчета задается одной из предопределенных констант, размещенных в заголовочном файле stdio.h:

    • SEEK_SET (имеет значение 0) - начало файла;

    • SEEK_CUR (имеет значение 1) - текущая позиция;

    • SEEK_END (имеет значение 2) - конец файла.

    Здесь следует напомнить некоторые особенности данных типа long. Этот тип определяется для целочисленных констант и пере­менных, которым в памяти должно быть выделено больше места, чем данным типа int. Обычно переменной типа long выделяется 4 байта, чем и определен диапазон ее значений. Описание данных типа long:

    long A, p, Z[16];

    Константа типа long записывается в виде последовательности де­сятичных цифр, вслед за которыми добавляется разделитель L или l. Примеры констант типа long:

    0l 0L 10L 688L 33l

    В текстах программ лучше использовать L, чтобы не путать l с цифрой 1.

    Функция fseek( ) возвращает 0, если перемещение в потоке (фай­ле) выполнено успешно, в противном случае возвращается ненуле­вое значение.

    Приведем примеры использования функции fseek( ). Перемеще­ние к началу потока (файла) из произвольной позиции:

    fseek(fp, 0L, SEEK_SET);

    Перемещение к концу потока (файла) из произвольной позиции:

    fseek(fp, 0L, SEEK_END);

    В обоих примерах смещение задано явно в виде нулевой десятич­ной константы 0L типа long.

    При использовании сложных типов данных (таких как структу­ра) можно перемещаться в потоке (файле) на то количество бай­тов, которое занимает этот тип данных. Пусть, например, определена структура:

    struct str

    {...

    ...

    } st;

    Тогда при следующем обращении к функции fseek( ) указатель текущей позиции в потоке будет перемещен на одну структуру назад относительно текущей позиции:

    fseek(fp, -(long)sizeof(st), SEEK_CUR);

    Для иллюстрации работы с файлом в режиме произвольного до­ступа рассмотрим пример небольшого трехъязычного словаря, со­держащего названия цифр от 1 до 9 на английском, немецком и французском языках. Для обслуживания словаря необходимы две программы: программа загрузки словаря в файл на диске и програм­ма поиска слова в словаре (в ней используется функция fseek( )).

    «База данных» словаря организована в файле vac.dat. Функция загрузки запрашивает названия для цифр от 1 до 9. Названия конк­ретной цифры (в предположении, что каждое название не превыша­ет 9 букв) на всех трех языках записываются в одной строке через пробелы. Сформированная строка по нажатии клавиши прочитывается в массив символов buf[ ]. Текст программы:

    #include

    int main()

    {

    int i, k;

    int n;

    char buf[30]; /* Буфер */

    char *c;

    int *pti;

    FILE *fp; /* Указатель на поток */

    /* Открыть файл для записи */

    if ((fp = fopen("vac.dat","w")) == NULL)

    {

    perror("vac.dat");

    return 1;

    }

    for (i=1; i<=9; i++)

    { /* Очистить буфер: */

    for (k=0; k<30; k++) buf[k] = ' ';

    /* Запрос названий цифр */

    n = i;

    pti = &n;

    printf("\n Введите названия цифры %d:\n", i);

    scanf("%s %s %s", buf, buf+10, buf+20);

    /* Запись в файл цифры */ c = (char *)pti;

    for (k=0; k <2; k++) putc(*c++, fp);

    /* Запись в файл названий цифр */ c = buf;

    for (k=0; k<30; k++) putc(*c++, fp);

    }

    fclose(fp);

    return 0;

    }


    Рис. 7.2. Состав записи «Цифра»
    Введенные данные (представление цифры в формате целого чис­ла и названия цифр на трех языках) побайтно выводятся в файл vac. dat, образуя в нем «записи», структура которых показана на рис. 7.2.

    2 байта

    10 байт

    10 байт

    10 байт


    Цифра Названия цифр на разных языках




    Следующая программа перевода (trans) запрашивает сначала язык, на котором нужно выводить названия цифр, а затем в цикле запрашиваются цифры. Язык идентифицируется первой буквой своего русского наименования: 'а' или 'А' - для английского; 'н' или 'Н' - для немецкого; 'ф' или 'Ф' - для французского. Признаком за­вершения программы служит ввод цифры 0. Текст программы trans:

    /* Программа перевода trans.c */

    #include int main()

    {

    int i, k;

    char *c;

    long pos; /* Смещение в файле */

    char buf[30];

    char ln;

    FILE *fp; /* Указатель на поток */

    int lang; /* Индикатор "язык" */

    /* Открыть файл: */

    if ((fp = fopen("vac.dat","r")) == NULL) {

    perror("vac.dat"); return 1;

    }

    /* Запросить язык: */ lang = -1;

    while (lang == -1) {

    puts("\n Введите первую букву названия" " языка: (а,н,ф)");

    scanf("%c", &ln);

    if ((ln =='a')||(ln =='А')) lang = 0; else

    if ((ln =='н')||(1п =='Н')) lang = 1; else

    if ((ln =='ф') 11 (ln =='Ф')) lang = 2;

    }

    while (1) /* Цикл перевода */

    {

    c = buf;

    puts("введите цифру (0 - для завершения):");

    scanf("%d", &k); if (k = = 0) {

    fclose(fp); return 0;

    }

    /* Вычислить смещение */ pos = (k-1)*32+2+lang*10; fseek(fp, pos, SEEK_SET);

    /* Выбрать перевод */ for (i=0; i<=9; i++)

    *c++=getc(fp);

    c++;

    • c = '\0';

    printf("%d->%s\n", k, buf);

    }

    fclose(fp);

    return 0;

    }

    При вычислении позиции (pos) в файле, с которой начинается строка перевода, участвуют следующие составляющие:

    • k-1 - выбирает подстроку (длиной 32 байта), в которой со­держится перевод;

    • 2 - учитывает длину поля цифры (2 байта);

    • lang* 10 - задает смещение до требуемого значения цифры в подстроке перевода.

    На рис. 7.3 показаны составляющие смещения для искомого поля с переводом на немецкий язык.

    2 байта

    10 байт

    10 байт

    10 байт


    к-й элемент


    pos




    Рис. 7.3. Составляющие смещения
    для записей в файле словаря


    Предлагаем самостоятельно разработать функцию поиска в файле заданной подстроки и на ее основе написать программу перевода названий цифр с одного языка на другой. В табл. 7.3 приводятся названия цифр от 1 до 9 на трех языках.

    Таблица 7.3. Трехъязычный словарь «Цифры»



    Английский язык

    Немецкий язык

    Французский язык

    1

    ONE

    EINS

    UN

    2

    TWO

    ZWEI

    DEUX

    3

    THREE

    DREI

    TROIS

    4

    FOUR

    VIER

    QUATRE

    5

    FIVE

    FUNF

    CINQ

    6

    SIX

    SECHS

    SIX

    7

    SEVEN

    SIEBEN

    SEPT

    8

    EIGHT

    ACHT

    HUIT

    9

    NINE

    NEUN

    NEUF

    Кроме рассмотренной функции fseek( ), в библиотеке функций языка Си находятся следующие функции для работы с указателями текущей позиции в потоке:

    • long ftell(FILE *) - получить значение указателя текущей по­зиции в потоке;

    • void rewind(FILE *) - установить указатель текущей позиции в потоке на начало потока.

    Необходимо иметь в виду, что недопустимо использовать функ­ции работы с указателем текущей позиции в потоке для потока, связанного не с файлом, а с устройством. Поэтому применение опи­санных выше функций с любым из стандартных потоков приводит к неопределенным результатам.

    7.2. Ввод-вывод нижнего уровня

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

    Функции ввода-вывода более низкого уровня позволяют пользо­ваться средствами ввода-вывода операционной системы непосред­ственно. При этом не выполняются буферизация и форматирование данных. Программы, использующие низкоуровневый ввод-вывод, переносимы в рамках некоторых систем программирования Си, в частности относящихся к UNIX. Учитывая близость функций низ­коуровневого ввода-вывода к средствам ввода-вывода операционной системы, можно рекомендовать их применение для разработки соб­ственной подсистемы ввода-вывода, например ориентированной на работу со сложными структурами данных (списки, деревья, слож­ные записи и т. п.).

    При низкоуровневом открытии файла с ним связывается не ука­затель файла (потока), а дескриптор (handle) файла. Дескриптор является целым значением, характеризующим размещение инфор­мации об открытии файла во внутренних таблицах операционной системы. Дескриптор файла используется при последующих опера­циях с файлом.

    В библиотеку языка Си включены следующие основные функции ввода-вывода нижнего уровня:

    • open( )/close( ) - открыть/закрыть файл;

    • creat( ) - создать файл;

    • read( )/write( ) - читать/писать данные;

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

    • eof( ) - проверить достижение конца файла;

    • lseek( ) - изменить текущую позицию в файле;

    • tell( ) - получить значение текущей позиции в файле.

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

    Функции нижнего уровня, в отличие от функций для работы с потоком, не требуют включения в программу заголовочного фай­ла stdio.h. Однако этот файл содержит определения ряда констант (например, признак конца файла EOF), которые могут оказаться по­лезными. В случае применения этих констант файл stdio.h должен быть включен в программу.

        1. Открытие/закрытие файла

    До выполнения операций ввода-вывода в файл (из файла) на низ­ком уровне необходимо открыть или создать файл одной из следую­щих функций: open( ), sopen( ) или creat( ).

    Функция sopen( ) используется в том случае, когда необходимо дать возможность одновременного доступа к файлу для нескольких выполняющихся программ. Разумеется, речь идет о доступе к файлу в режиме чтения. Обычно файл блокируется для доступа со стороны других выполняющихся программ, и именно функция sopen( ) не­обходима для разрешения одновременного доступа.

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

    Формат вызова функции open( ), в результате выполнения кото­рой приобретает значение дескриптор файла:

    fd = open ( имя_файла, флаги, права_доступа );

    В программе дескриптор файла fd должен быть определен как int fd. Параметр имя_файла является указателем на массив символов, содержащий имя файла.

    Второй параметр флаги определяет режим открытия файла, кото­рый является выражением, сформированным (с помощью '|' - по­битовой операции ИЛИ) из одной или более предопределенных констант, размещенных в заголовочном файле fcntl.h. В некоторых реализациях UNIX эти константы находятся в файле sys/file.h.

    Примечание

    Обратите внимание на то, что в UNIX при образовании полного имени файла применяется символ '/' (прямой слэш).

    Приведем в алфавитном порядке список констант (флагов), за­дающих режим открытия файла:

    • O_APPEND - открыть файл для добавления (для записи в конец файла);

    • O_BINARY - открыть файл в бинарном режиме (см. §7.1.1);

    • O_CREAT - создать и открыть новый файл;

    • O_EXCL - этот флаг позволяет избежать непреднамеренно­го уничтожения уже существующего файла. Если этот флаг указан вместе с флагом O_CREAT и файл уже существует, то функция открытия файла завершается с ошибкой;

    • O_RDONLY - открыть файл только для чтения;

    • O_RDWR - открыть файл и для чтения, и для записи;

    • O_TEXT - открыть файл в текстовом режиме (см. §7.1.1);

    • O_TRUNC - открыть существующий файл и стереть его со­держимое (подготовить для записи новой информации).

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

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

    В операционных системах семейства Windows для задания пара­метра права_доступа используются следующие предопределенные константы:

    • S_IWRITE - разрешить запись в файл;

    • S_IREAD - разрешить чтение из файла;

    • S_IREAD|S_IWRITE - разрешить и чтение, и запись.

    Перечисленные константы размещены в заголовочном файле stat.h, находящемся в каталоге sys. Обычно его подключение осу­ществляется директивой #include <sys\stat.h>.

    Если параметр права_доступа не указан, то устанавливается раз­решение только на чтение из файла. Чаще всего в операционных системах семейства Windows этот параметр не используется.

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

    • владелец файла;

    • участник группы пользователей;

    • прочие пользователи.

    Права доступа к конкретному файлу устанавливаются владельцем файла специальными командами. Права доступа отображаются при просмотре оглавления каталога командой ls -l в виде символьной строки, которая формируется по следующему правилу: для каждой группы пользователей в строке прав доступа выделяются три сим­вола, каждый из которых может принимать следующие значения:

    • r - разрешено чтение из файла;

    • w - разрешена запись в файл;

    • x - разрешено выполнение файла (для файлов, хранящих ис­полняемую программу).

    Символы r, w, x задаются строго на своих местах в указанном порядке (rwx). Если какой-либо из типов доступа к файлу запре­щен, на месте соответствующего символа записывается символ '-' (минус). Таким образом, если для владельца файла разрешены все виды доступа к файлу (rwx), для участника группы пользовате­лей - только чтение и выполнение (r-x), а для прочих пользовате­лей - только выполнение (--x), то строка прав доступа будет вы­глядеть так:

    rwxr-x--x

    От этой строки символов легко перейти к собственно параметру права_доступа, являющемуся целым числом. Если на соответст­вующем месте в строке указан символ, отличный от '-', то запи­сывают '1', иначе записывают '0'. Получившееся двоичное число (111101001) переводят в восьмеричное, записав в виде восьмерич­ной цифры каждую группу из трех двоичных цифр, начиная с са­мой правой группы: 0751. Это число и следует указать в качестве параметра права_доступа в функции open( ).

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

    1. Открыть файл для чтения:

    fd = open("t.txt", O_RDONLY);

    1. Открыть существующий файл для записи новых данных:

    fd = open("new.txt",O_WRONLY|O_CREAT|O_TRUNC,0600);

    Параметр права_доступа, заданный восьмеричной константой 0600 (для UNIX), в символьном изображении имеет вид rw ,

    то есть для владельца файла разрешены чтение и запись, для двух других категорий пользователей не разрешен ни один из видов доступа к файлу. Если файл с именем new.txt существует, то перед записью новых данных он будет усечен до нулевого размера (очищен). При открытии файла с указанными пара­метрами в других ОС параметр права_доступа опускается.

    1. Открыть файл для добавления:

    fd=open("t.txt",O_WRONLY|O_APPEND|O_CREAT,0600);

    1. Открыть файл для чтения и записи:

    fd = open("t.txt", O_RDWR);

    1. Создать новый файл для записи:

    if ((fd = open("tmpfile",O_WRONLY|O_CREAT|O_EXCL, 0666)) == -1) puts("tmpfile уже существует\п");

    В операционной системе UNIX такая последовательность операто­ров открывает новый файл для записи. Если файл не существует, то он создается. Иначе функция завершается неудачей. Флаг O_EXCL специально задан для предотвращения непреднамеренного унич­тожения уже существующего файла. Этот флаг используется со­вместно с O_CREAT. Права доступа (rw-rw-rw-) разрешают чтение и запись в файл для всех категорий пользователей. В других ОС параметр права_доступа должен быть опущен.

    Приведем более полный пример создания файла (MS-DOS, Win­dows):

    #include

    #include

    #include

    #include

    #include

    #include void main( )

    { int fd; /* Дескриптор файла */ if ((fd = open("tmpfile",

    O_RDWR|O_CREAT|O_EXCL,

    S_IREAD|S_IWRITE)) < 0) {

    if (errno == EEXIST) fprintf(stderr, "файл tempfile"

    " уже существует\п");

    exit(errno);

    }

    }

    Создаваемый файл в соответствии с выбранными флагами откры­вается для чтения и записи. Права доступа (rw ) позволяют

    только владельцу работать с файлом (читать и писать).

    Для идентификации ошибок, возникающих при открытии фай­ла, используется именующее выражение (переменная) errno, опре­деленное в заголовочном файле errno.h. При выполнении функ­ций стандартной библиотеки в область памяти, именуемой errno, записываются коды ошибок. Предопределенная в errno.h константа EEXIST означает, что файл, указанный в функции open( ), уже су­ществует. В этом примере для вывода сообщения об ошибке при­менена функция форматного вывода в файл fprintf( ), в которой ис­пользован предопределенный дескриптор файла stderr стандартного потока для вывода сообщений об ошибках.

    Кроме функции open( ), для открытия файла можно использо­вать функцию creat( ), упомянутую в начале параграфа. Функция creat( ) полностью эквивалентна такому вызову функции open( ):

    open (имя_файла, O_CREAT|O_TRUNC|O_WRONLY);

    Функция creat( ) создает новый файл и открывает его для запи­си. Наличие в библиотеке наряду с функцией open( ) функции creat( ) вызвано требованиями совместимости с ранними версия­ми UNIX, имевшими только три основных режима открытия файла (O_RDONLY, O_WRONLY, O_RDWR), что вынуждало исполь­зовать для создания нового файла специальную функцию creat( ).

    Так же как и при использовании потоков, в начале работы каждой программы автоматически открываются файлы стандартного ввода, стандартного вывода и стандартного вывода сообщений об ошибках. Эти файлы имеют значения дескрипторов файлов 0, 1 и 2, которые можно использовать при обменах на нижнем уровне со стандарт­ными файлами.

    Необходимо иметь в виду, что в каждой операционной системе имеется ограничение на количество одновременно открытых в про­грамме файлов. Обычно их число устанавливается от 20 до 40. Во время работы программы, в которой обрабатывается большое ко­личество файлов, необходимо своевременно закрывать ненужные файлы. Для закрытия файла на нижнем уровне служит функция close( ), прототип которой имеет вид:

    int close (дескриптор_файла);

    Функция close( ) при успешном завершении возвращает 0. В слу­чае ошибки возвращается -1.

    При завершении программы все открытые файлы автоматически закрываются.

        1. Чтение и запись данных

    Ввод-вывод данных на нижнем уровне осуществляется функциями read( ) и write( ). Прототипы этих функций имеют следующий вид:

    int read(int fd, char *buffer, unsigned int count);

    int write(int fd, char *buffer, unsigned int count);

    Обе функции возвращают целое число - количество действитель­но прочитанных или записанных байтов.

    Функция read( ) читает количество байтов, заданное третьим параметром count, из файла, открытого с дескриптором файла fd, в буфер, определенный указателем buffer. При достижении конца файла функция read( ) возвращает значение 0. В случае возник­новения ошибки при чтении из файла функция read( ) возвращает значение -1.

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

    Если файл открыт в текстовом режиме, то происходят точно та­кие же преобразования при вводе последовательности символов CR и LF в символ '\n' (LF), как и при работе с потоком. Указанное пре­образование приводит к тому, что в возвращаемом значении вместо двух символов CR и LF учитывается только один символ '\n' (LF).

    Функция write( ) записывает последовательность байтов, коли­чество которых задано третьим параметром count, в файл, откры­тый с дескриптором файла fd, из буфера, определенного указателем buffer. Запись производится с текущей позиции. Если файл открыт в текстовом режиме, то количество реально записанных байтов может превышать count за счет преобразований всех символов '\n' в по­следовательности символов CR, LF.

    Если при выполнении операции записи возникла ошибка, то функция write( ) возвращает значение -1, а глобальная переменная errno получает одно из следующих значений, заданных предопреде­ленными константами в заголовочном файле errno.h:

    • EACCES - файл защищен для записи (доступен только для чтения);

    • ENOSPC - исчерпано свободное пространство на внешнем устройстве;

    • EBADF - недействительный дескриптор файла.

    Приведем два примера применения функций низкоуровневого ввода-вывода.

    Пример 1. Копирование последовательности отдельных символов из стандартного ввода в стандартный вывод:

    #include

    int main( )

    {

    char c[2];

    while ((read(0, c, 1)) > 0)

    write(1, c, 1);

    return 0;

    }

    В текст программы включается заголовочный файл io.h, содер­жащий прототипы функций read( ) и write( ). При вызове этих функций для файлов стандартного ввода и стандартного вывода используются соответственно значения дескрипторов стандартных файлов 0 и 1. Прочитанный символ и код клавиши , ко­торый служит признаком завершения набора вводимой последо­вательности символов, записываются в одномерный массив c[ ] из 2 байтов, откуда они затем функцией write( ) выводятся на экран дисплея.

    Запустив программу на выполнение, можно вводить одиночные символы с клавиатуры, завершая ввод каждого из них нажатием на клавишу . Результат работы программы может выглядеть так:

    V

    v

    w

    w

    e

    e



    Первый символ из пары одинаковых символов (v-v; w-w и т. д.) - это символ, введенный с клавиатуры и выведенный системой ввода- вывода на экран (стандартный режим ввода данных с клавиатуры). Второй символ пары выведен на устройство стандартного вывода функцией write( ). Программа копирования завершает работу при вводе сигнала прерывания (одновременном нажатии клавиш и ).

    Пример 2. Копирование произвольного файла.

    Программа получает имена файлов из командной строки при за­пуске и позволяет копировать произвольные файлы (см. аналог этой программы в §7.1.3).

    #include

    #include

    #include

    int main(int argc, char *argv[ ])

    {

    int fdin, fdout; /* Дескрипторы файлов */;

    int n; /* Количество прочитанных байтов */

    char buff[BUFSIZ];

    if ( argc != 3)

    {

    printf("Формат вызова программы:");

    printf("\n %s файл_источник файл_приемник", argv[0]); return 1;

    }

    if ((fdin = open(argv[1], O_RDONLY)) == -1)

    {

    perror(argv[1]);

    return 1;

    }

    if ((fdout = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC)) == -1)

    {

    perror(argv[2]);

    return 1;

    }

    /* Файлы открыты - можно копировать */

    while ((n = read(fdin, buff, BUFSIZ)) > 0 )

    write(fdout, buff, n);

    return 0;

    }

    Константа BUFSIZ (размер буфера для потокового ввода-выво­да) определена в заголовочном файле stdio.h. Ее значение обычно равно 512 байт.

    Пусть программа откомпилирована и создан исполняемый файл с именем, например, copyf.exe. Вызов программы из командной стро­ки будет иметь вид:

    >copyf f1.dat f2.dat

    где f1.dat - файл-источник; f2.dat - файл-приемник.

    Если ошибок при исполнении программы нет, файл будет скопи­рован, но никаких сообщений на экране дисплея не появится.

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

    В приведенных выше программах с функциями ввода-вывода низкого уровня обмен с файлом осуществлялся последовательно. При необходимости файл можно читать на низком уровне и в про­извольном порядке. Так же как это делалось при работе с потоками, можно изменять значение указателя текущей позиции чтения/запи- си в файле. Для этой цели служит функция lseek( ). Прототип этой функции имеет следующий вид:

    long lseek(int fd, long offset, int origin);

    Функция lseek( ) изменяет текущую позицию в файле, связан­ном с дескриптором fd, на новую, определяемую смещением (второй параметр - offset) относительно выбранной точки отсчета (третий параметр - origin).

    Точка отсчета задается одной из предопределенных констант, раз­мещенных в заголовочном файле io.h или файле unistd.h (UNIX):

    • SEEK_SET (имеет значение 0) - начало файла;

    • SEEK_CUR (имеет значение 1) - текущая позиция;

    • SEEK_END (имеет значение 2) - конец файла.

    При удачном завершении функция lseek( ) возвращает новую те­кущую позицию чтения/записи, представляющую собой смещение от начала файла. Попытка переместиться за пределы файла считает­ся ошибкой. Код ошибки заносится в глобальную переменную errno, определенную в заголовочном файле errno.h.

    Для определения текущей позиции в файле можно использовать функцию tell( ), прототип которой имеет следующий вид:

    long tell(int fd);

    Приведем примеры использования функции lseek( ).

    Пример 1. Установка текущей позиции в файле на его начало:

    lseek(fd, 0L, SEEK_SET);

    Пример 2. Установка текущей позиции для последующего добав­ления данных в файл (позиция в конце файла):

    lseek(fd, 0L, SEEK_END);

    Пример 3. Модификация записей в существующем файле.

    Запись - это последовательность байтов в файле, представляю­щая сложный структурный элемент в контексте программы, обра­батывающей эти записи. Логическая структура записи может быть любой: массивом, строкой, структурой и т. д. В следующем фраг­менте программы предполагается, что все записи имеют одинаковый размер (в байтах) и размещены в файле подряд.

    /* Прочитать запись в буфер */ read(fd, buff, sizeof( запись ));

    /* Вернуть указатель в файле на место,

    с которого начиналось чтение записи */ lseek(fd, -sizeof( запись ), SEEK_CUR);

    /* Откорректировать запись в буфере */

    /* Поместить запись на прежнее место */ write(fd, buff, sizeof( запись ));

    Буфер buff может быть определен как массив символов, достаточ­ный для размещения одной записи.

    Контрольные вопросы

    1. Каким образом при отсутствии в языке Си средств ввода-вывода реализуются операции ввода-вывода?

    2. Дайте определение файла в языке Си.

    3. Что означает термин «потоковый ввод-вывод»?

    4. Дайте определение потоку.

    5. Какие действия можно производить при работе с потоком?

    6. Для чего служит структура предопределенного типа FILE?

    7. Укажите различия между именем файла и указателем типа FILE.

    8. Укажите имя функции и перечислите ее параметры, необходи­мые для открытия потока в текстовом режиме.

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

    10. Какие изменения происходят в структуре типа FILE при откры­тии файла?

    11. Перечислите стандартные потоки и функции для работы с ними.

    12. Перечислите функции для ввода-вывода строк и форматного ввода-вывода.

    13. Какие функции включены в библиотеку Си для работы с фай­лами на диске?

    14. Для чего служат средства позиционирования в потоке?

    15. В чем состоит отличие ввода-вывода низкого уровня от потоко­вого ввода-вывода?

    16. Перечислите режимы, в которых можно открыть низкоуровне­вый файл.

    17. Что означает параметр открытия файла «права доступа» и как он применяется?

    18. Каким образом осуществляется произвольный доступ к низко- уровнему файлу?

    19. Что необходимо сделать, для того чтобы изменить режим об­работки файла?

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

    21. В какую точку потока (на начало, в конец) устанавливается ука­затель чтения/записи в режимах «r», «w» и «a»?

    22. Какие точки отсчета выбирают при смещениях по файлу?

    1   ...   21   22   23   24   25   26   27   28   ...   42


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