Язык Си - Уэйт, Прата, Мартин. M. уэит с. Прата д. Мартин
Скачать 4.69 Mb.
|
БИБЛИОТЕЧНЫЕ ФУНКЦИИ, КОТОРЫЕ МЫ ИСПОЛЬЗОВАЛИ Далее Содержание Пока мы хотим только перечислить эти функции, чтобы напомнить о них. Сначала приведем функции ввода-вывода: getchar( ) /* получение символа */ putchar( ) /* печать символа */ gefs( ) /* получение строки */ puts( ) /* печать строки */ scanf( ) /* получение форматированного ввода */ printf( ) /* печать форматированного вывода */ Затем приведем функции, работающие со строками: strlen( ) /* нахождение длины строки */ strcmp( ) /* сравнение двух строк */ strcpy( ) /* копирование строки */ strcat( ) /* объединение двух строк */ К этому списку мы добавим функции открытия и закрытия файлов, связи с файлами, проверки и преобразования символов, преобразования строк, функцию выхода и функции распределения памяти. Давайте сначала обратимся к проблеме связи между файлом и программой. СВЯЗЬ С ФАЙЛАМИ Далее Содержание Часто нам бывает нужна программа получения информации от файла или размещения результатов в файле. Один способ организации связи программы с файлом заключается в использовании операций переключения < и >. Этот метод прост, но ограничен. Например, предположим, вы хотите написать диалоговую программу, которая спрашивает у вас названия книг (звучит фамильярно?), и вы намерены сохранить весь список в файле. Если вы используете переключение как, например, в books > bklist то ваши диалоговые приглашения также будут переключены на bklist. И тогда не только нежелательная чепуха запишется в bklist, но и пользователь будет избавлен от вопросов, на которые он, как предполагалось, должен отвечать. К счастью, язык Си предоставляет и более мощные методы связи с файлами. Один подход заключается в использовании функции fopen( ), которая открывает файл, затем применяются специальные функции ввода-вывода для чтения файла или записи в этот файл и далее используется функция fclose( ) для закрытия файла. Однако прежде чем исследовать эти функции, нам нужно хотя бы кратко познакомиться с сущностью файла. 299 ЧТО ТАКОЕ ФАЙЛ? Далее Содержание Для нас файл является частью памяти, обычно на диске, со своим именем. Мы считаем, например, stdio.h именем файла, содержащего некоторую полезную информацию. Для операционной системы файл более сложен, но это системные проблемы, а не наши. Однако мы должны знать, что означает файл для программы на языке Си. В предлагаемых для обсуждения функциях, работающих с файлами, язык Си "рассматривает" файл как структуру. Действительно, файл stdio.h содержит определение структуры файла. Вот типичный пример, взятый из IBM-версии компилятора Lattice С: struct _ iobuf { char *_ptr; /* текущий указатель буфера */ int_cnt; /* текущий счетчик байтов */ char*_base; /* базовый адрес буфера ввода-вывода*/ char_flag; /* управляющий признак */ char _ file; /* номер файла */ }; #define FILE struct_iobuf /* краткая запись */ Здесь мы не собираемся разбираться детально в этом определении. Главное состоит в том, что файл является структурой, и что краткое наименование шаблона файла - FILE. (Многие системы используют директиву typedef для установления этого соответствия.) Таким образом, программа, имеющая дело с файлами, будет использовать тип структуры FILE, чтобы делать так. Имея это в виду, мы сможем лучше понять операции над файлами. ПРОСТЫЕ ПРОГРАММЫ ЧТЕНИЯ ФАЙЛА: fopen( ), fclose( ), getc( ) и putc( ) Далее Содержание Чтобы показать элементарные примеры использования файлов, мы составили небольшую программу, которая читает содержимое файла, названного test, и выводит его на экран. Вы найдете наши пояснения сразу после программы. /* расскажите, что находится в файле "test" */ #include main( ) { FILE *in; /* описываю указатель на файл */ int ch; if((in = fopen("test", "r"))!=NULL) /* открываю test для чтения, проверяя существует ли он */ /* указатель FILE ссылается теперь на test */ { while((ch = getc(in) != EOF) /* получаю символ из in */ putc(ch, stdout); /* посылаю на стандартный вывод */ fclose(in); /* закрываю файл */ } else printf("я не смогла открыть файл \" test\" .\n"); } Следует объяснить три основных момента: работу fopen( ), работу fclose( ) и использование функций ввода-вывода файла. Займемся ими. Открытие файла: fopen( ) Далее Содержание 300 Функцией fopen( ) управляют три основных параметра. Первый - имя файла, который следует открыть. Он является и первым аргументом fopen( ); в нашем случае это "test" . Второй параметр [и второй аргумент fopen( )] описывает, как должен использоваться файл. Вот три основных применения файла: "r": файл нужно считать "w": файл нужно записать "а": файл нужно дополнить Некоторые системы предоставляют еще дополнительные возможности, но мы будем придерживаться этих. Заметим, что используемые нами коды являются строками, а нe символьными константами; следовательно, они заключаются в двойные кавычки. При применении "r" открывается существующий файл. При двух других применениях тоже будет открываться существующий файл, но если такого файла нет, он будет создан. Внимание: Если вы используете "w" для существующего файла, то старая версия его стирается, и ваша программа начинает на "чистом месте". Третий параметр является указателем на файл; это значение возвращается функцией: FILE *in; in = fopen("test", "r"); Теперь in является указателем на файл "test". С этого момента программа ссылается на файл при помощи указателя in, а не по имени test. Если вы очень сообразительны, то теперь можете задать такой вопрос: "Если fopen( ) возвращает указатель на 'FILE' в качестве аргумента, то почему мы не должны объявить fopen( ) как функцию указателя на 'FILE' ?" Хороший вопрос. Ответ заключается в том, что это описание сделал для нас файл stdio.h, который содержит строку FILE *topen( ); Есть еще один важный момент относительно функции fopen(), которую мы использовали. Если fopen() не способна открыть требуемый файл, она возвращает значение 'NULL' (определенное в stdio.h как 0). Почему она не смогла открыть файл? Вы можете попросить ее считать файл, который не существует. Вот почему мы имеем в программе строку if((in=fopen("test", "r"))!= NULL) Заполнение диска, использование запрещенного имени или некоторые другие причины могут препятствовать открытию файла. Поэтому побеспокойтесь, чтобы их не было - маленькая ошибка может увести вас очень далеко. Закрыть файл проще. Закрытие файла: fclose( ) Далее Содержание Наш пример показывает, как закрывать файл: fclose(in); Просто используйте функцию fclose( ). Заметим, что аргументом ее является in, указатель на файл, а не test, имя файла. 301 Для программы, более серьезной, чем эта, следовало бы посмотреть, успешно ли закрыт файл. Функция fclose( ) возвращает значение 0, если файл закрыт успешно, и -1 в противном случае. Текстовые файлы с буферизацией Далее Содержание Функции fopen() и fclose() работают с текстовыми файлами с "буферизацией". Под буферизацией мы понимаем, что вводимые и выводимые данные запоминаются во временной области памяти, называемой буфером. Если буфер заполнился, содержимое его передается в блок, и процесс буферизации начинается снова. Одна из основных задач fclose( ) заключается в том, чтобы "освободить" любые частично заполненные буфера, если файл закрыт. Текстовым считается файл, в котором информация запоминается в виде символов в коде ASCII (или аналогичном). Он отличается от двоичного файла, который обычно используется для запоминания кодов машинного языка. Функции ввода-вывода, о которых мы собираемся рассказать, предназначены только для работы с текстовыми файлами. Ввод-вывод файла: getc( ) и putc( ) Далее Содержание Две функции getc( ) и putc( ) работают аналогично функциям getchar( ) и putchar( ). Разница заключается в том, что вы должны сообщить новичкам, какой файл следует использовать. Таким образом, наш "старый дружище" ch = getchar( ); предназначен для получения символа от стандартного ввода, а ch = getc(in); - для получения символа от файла, на который указывает in. Аналогично функция putc(ch, out); предназначена для записи символа ch в файл, на который ссылается указатель out типа FILE. В списке аргументов функции putc( ) этот символ стоит первым, а затем указатель файла. В нашем примере мы использовали putc(ch, stdout); где stdout является указателем на стандартный вывод. Таким образом, этот оператор эквивалентен putchar(ch); Действительно, оператор putchar(ch) определен директивой #define так же как функция putc(ch, stdout) определена в файле stdio.h. Этот ужасный файл к тому же определяет в директиве #define указатели stdout и stdin на стандартный вывод и стандартный ввод системы. Это выглядит довольно просто? Хорошо, добавим пару полезных новшеств. ПРОСТАЯ ПРОГРАММА СЖАТИЯ ФАЙЛА Далее Содержание В нашем примере имя файла, который следовало открыть, было записано в программе. Мы нс обязаны считаться с этим ограничением. Используя аргументы командной строки, можно сообщить нашей программе имя файла, который хотим считать. B нашем следующем примере 302 (рис. 15.1) так и происходит. С помощью примитивного приема сжимается содержимое - остается только каждый третий символ. Наконец, сжатая версия размещается в новый файл, имя которого состоит из старого имени с добавкой .red (сокращение слова reduced). Обычно весьма важны первый и последний элементы (аргумент командной строки и добавка к имени файла). Само же сжатие имеет более ограниченное применение, но, как вы увидите, им можно пользоваться. /* сожмите ваши файлы в 2-3 раза! */ #include main(argc, argv) int argc; char *argv[ ]; { FILE *in, *out; /* описывает два указателя типа FILE */ int ch; static char name[20]; /* память для имени выходного файла */ int count = 0; if(argc < 2) /* проверяет, есть ли входной файл */ printf(" Извините, мне нужно имя файла в качестве аргумента.\n" ); else { if((in = fopen(argv[1], "r")) != NULL) { strcpy(name, argv[1]; /* копирует имя файла в массив */ strcat(name, ".red"); /* добавляет .red к имени */ out = fopen(name, "w"); /* открывает файл для записи */ while((ch = getc(in)) ! = EOF) if( count ++ %3 ==0) putc(ch, out); /* печатает каждый третий символ */ fclose(in); fclosе(out); } else printf(" Я не смогла открыть файл\" %s\" .\n", argv[1]); } } РИС. 15.1. Программа сжатия файла. Мы поместили программу в файл, названный reduce и применили эту программу к файлу, названному eddy, который содержал одну единственную строку Даже Эдди нас опередил с детским хором. Была выполнена команда reduce eddy и на выходе получен файл, названный eddy.red, который содержит Дед спел тихо Какая удача! Наш случайно выбранный файл сделал осмысленное сокращение. Вот некоторые замечания по программе. Вспомните, что argc содержит определенное количество аргументов, в число которых входит имя программного файла. Вспомните также, что с согласия операционной системы argv[0] представляет имя программы, т. е. в нашем случае reduce. Вспомните еще, что argv[l] представляет первый аргумент, в нашем случае eddy. Так как сам argv[l] является указателем на строку, он не должен заключаться в двойные кавычки в операторе вызова функции. Мы используем argc, чтобы посмотреть, есть ли аргумент. Любые избыточные аргументы игнорируются. Помещая в программу еще один цикл, вы могли бы использовать дополнительные аргументы - имена файлов и пропускать в цикле каждый из этих файлов по очереди. С целью создания нового имени выходного файла мы используем функцию strcpy( ) для 303 копирования имени eddy в массив nаmе. Затем применяем функцию strcat( ) для объединения этого имени с .red. Программа требует, чтобы два файла были открыты одновременно, поэтому мы описали два указателя типа 'FILE '. Заметим, что каждый файл должен открываться и закрываться независимо от другого. Существует ограничение на количество файлов, которые вы можете держать открытыми одновременно. Оно зависит от типа системы, но чаще всего находится в пределах от 10 до 20. Можно использовать один и тот же указатель для различных файлов при условии, что они нс открываются в одно и то же время. Мы не ограничиваемся использованием только функций getc( ) и putc( ) для файлов ввода- вывода. Далее мы рассмотрим некоторые другие возможности. ВВОД-ВЫВОД ФАЙЛА: fprintf( ), fscanf( ), fgets( ) И fputs( ) Далее Содержание Все функции ввода-вывода, которые мы использовали в предыдущих главах, имеют аналоги для ввода-вывода файла. Основное отличие состоит в том, что вам нужно использовать указатель типа FILE, чтобы сообщить новым функциям, с каким файлом им следует работать. Подобно getc( ) и putc( ) эти функции используются после функции fopen( ), открывающей файл, и перед fclose( ), закрывающей его. Функции fprintf( ) и fscanf( ) Далее Содержание Эти функции ввода-вывода работают почти как printf( ) и scanf( ), но им нужен дополнительный аргумент для ссылки на сам файл. Он является первым в списке аргументов. Вот пример, иллюстрирующий обращение к этим функциям: /* образец использования fprintf( ) и fscanf( ) */ #include { FILE *fi; int age; fi = fopen("sam", "r"); /* считывание */ fscanf(fi, "%d", &age); /* fi указывает на sam */ fclose(fi); fi = fopen("data", "a"); /* дополнение "/ fprintf(fi, "sam is %d. \n", age); /* fi указывает на data */ fclose(fi); } Заметим, что можно было использовать fi для двух различных файлов, потому что мы закрыли первый файл, прежде чем открыть второй. В отличие от getc( ) и putc( ) эти две функции получают указатель типа FILE в качестве первого аргумента. Две другие, описанные ниже, получают его в качестве последнего аргумента. Функция fgets( ) Далее Содержание Эта функция имеет три аргумента, в то время как gets( ) имеет один. Вот пример ее использования: /* считывает файл строка за строкой */ 304 #include #define MAXLIN 80 main( ) { FILE *f1; char *string [MAXLIN]; f1 = fopen("story", "r"); while(fgets(string, MAXLIN, f1) != NULL) puts(string); } Первый из трех аргументов функции fgets( ) является указателем на местоположение считываемой строки. Мы располагаем вводимую информацию в символьный массив string. Второй аргумент содержит предельную длину считываемой строки. Функция прекращает работу после считывания символа новой строки или после считывания символов общим числом MAXLIN - 1 (в зависимости от того, что произойдет раньше). В любом случае нуль-символ ('\0') добавляется в самый конец строки. Третий аргумент указывает, конечно, на файл, который будет читаться. Разница между gets( ) и fgets( ) заключается в том, что gets( ) заменяет символ новой строки на '\0', в то время как fgets( ) сохраняет символ новой строки. Подобно gets( ) функция fgets( ) возвращает значение NULL, если встречает символ EOF. Это позволяет вам проверить, как мы и сделали, достигли ли вы конца файла. Функция fputs( ) Далее Содержание Эта функция очень похожа на puts( ). Оператор fputs(" Вы были правы.", fileptr); передает строку "Вы были правы." В файл, на который ссылается указатель fileptr типа FILE. Конечно, сначала нужно открыть файл при помощи функции fopen( ). В самом общем виде это выглядит так status = fputs(yказатель строки, указатель файла); где status является целым числом, которое устанавливается в EOF, если fputs( ) встречает EOF или ошибку. Подобно puts( ) эта функция не ставит завершающий символ '\0' в конец копируемой строки. В отличие от puts() функция fputs( ) не добавляет символ новой строки в ее вывод. Шесть функций ввода-вывода которые мы только что обсудили, должны дать вам инструмент, для чтения и записи текстовых файлов. Есть еще одно средство, которое может оказаться полезным, и мы его сейчас обсудим. ПРОИЗВОЛЬНЫЙ ДОСТУП: fseek( ) Далее Содержание Функция fseek( ) позволяет нам обрабатывать файл подобно массиву и непосредственно достигать любого определенного байта в файле, открытом функцией fopen( ). Вот простой пример, показывающий, как она работает. Как и в наших предыдущих примерах, функция использует аргумент командной строки для получения имени файла, с которым она работает. Заметим, что fseek() имеет три аргумента и возвращает значение типа int. 305 /*использование fscek( ) для печати содержимого файла */ #include int number; char *namеs[ ]; { FILE *fp; long offset = OL; /*обратите внимание, что это тип long */ if(number < 2) puts("Мне нужно имя файла в качестве аргумента."); else { if((fp = fopen(names[1], "r")) == 0) printf(" Я не могу открыть %s.\n", names[1]); else { while(fseek(fp, offset++, 0) == 0) putchar(getc(fp)); fclose(fp); } } } Перый из трех аргументов функции fseek( ) является указателем типа FILE на файл, в котором ведется поиск. Файл следует открыть, используя функцию |