|
|
|
|
Тема 13. Стандартные библиотеки языка Си |
Система программированиz в Си содержит ряд предварительно определенных функций и макроопределений, находящихся в составе заголовочных файлов. Сами библиотеки не являются частью языка, а составляют системную среду поддерживающую стандарт Си.
В соответствии со стандартами ANSI должно быть 15 обязательных заголовочных файлов.
Таблица 1
Имя заголовочного файла |
Назначение |
assert.h |
Диагностика программы |
ctype.h |
Преобразование и проверка типов |
errno.h |
Проверка ошибок |
float.h |
Функции для работы с типом с плавающей точкой |
limits.h |
Предельные значения целых типов |
locale.h |
Поддержка интернациональной среды (содержит прототипы функций, позволяющих модифицировать программы в зависимости от географического места ее выполнения) |
math.h |
Математические функции |
setjmp.h |
Возможности нелокальных переходов (содержит прототипы функций, которые позволяют обходить обычную последовательность вызова функции и возврата из нее) |
signal.h |
Обработка сигналов |
stdarg.h |
Поддержка функций с неопределенным числом параметров |
stddef.h |
Разное (содержит объявления типов, используемых для некоторых вычислений) |
stdio.h |
Библиотека стандартного ввода-вывода |
stdlib.h |
Функции общего назначения (выделение памяти и т.д.) |
string.h |
Функции для работы со строками |
time.h |
Функции для работы с датами и временем |
13.1. Стандартная библиотека ввода-вывода. Работа с файлами |
В языке Си нет встроенных средств ввода-вывода. Все эти действия выполняются соответствующими функциями ввода-вывода.
Библиотека Си поддерживает три уровня ввода-вывода:
1. Ввод-вывод потока
2. Ввод-вывод нижнего уровня
3. Ввод-вывод для консоли и порта
13.1.1. Ввод-вывод потока |
При вводе-выводе потока все данные рассматриваются как поток отдельных байтов, не зависимо от того, какое используется устройство ввода-вывода, т.е. поддерживается некоторый абстрактный уровень между программой и физическим устройством. Эта абстракция и называется потоком.
Поток – это источник или получатель данных. Для пользователя поток- это либо файл на диске, либо физическое устройство.
Т.к. потоки не зависят от физических устройств, то одна и та же функция может записать информацию на диск или вывести ее на экран либо принтер.
Существует два типа потоков:
текстовый
бинарный
Текстовый поток – это последовательность строк, каждая из которых содержит 0 или более символов и заканчивается символами возврата каретки ’\n’.
В текстовом потоке может не быть однозначного соответствия между символами, которые передаются в потоке и символами, которые выводятся на внешнее устройство. ОС может потребовать коррекции текстового потока (например, пара символов « возврат каретки – перевод строки» преобразуется в одиночный символ перевода строки на вводе, а комбинация клавиш Ctrl-z (0x1a) интерпретируется как символ конца потока на вводе)
Бинарный поток – это последовательность непреобразуемых байтов, которые взаимнооднозначно соответствуют тому, что выводится на внешнее устройство.
Поток связывается с физическим устройством или файлом путем его открытия. Как только файл открыт, то информация может передаваться между ним и программой. Эта связь разрывается путем закрытия потока. Открытие потока возвращает указатель на структуру типа FILE.
С началом выполнения программы автоматически открывается 5 потоков, которым соответствуют предопределенные указатели потоков:
1. stdin -стандартный ввод
2. stdout -стандартный вывод
3. stderr -стандартный вывод сообщений об ошибках
4. stdaux -стандартный дополнительный поток (COM1)
5. stdprn -стандартная печать (LPT1)
По умолчанию первые три (stdin, stdout и stderr) связаны с консолью пользователя и открываются в текстовом режиме. Это означает, что каждый раз, когда программа ожидает ввод со стандартного ввода, данные должны вводиться с консоли. Т.е. стандартный ввод – это ввод с клавиатуры, а стандартный вывод- это вывод на экран.
Назначения стандартному дополнительному потоку и стандартной печати зависят от конфигурации машины. Обычно stdaux связывается с дополнительным портом COM1, а stdprn – связывается с портом LPT1, к которому подключен принтер. Два последних потока открываются в двоичном режиме.
РЕКОМЕНДУЕТСЯ
|
Пользуйтесь преимуществами стандартных потоков ввода-вывода Си при написании своих программ. |
НЕ РЕКОМЕНДУЕТСЯ
|
Не переименовывайте и не заменяйте стандартные потоки без особой необходимости. Не пытайтесь воспользоваться потоком ввода stdin для целей вывода, например с помощью функции fprintf(). |
Режим доступа к файлам задается во время открытия файла как параметр функции открытия файла или специальной внешней переменной _fmode, описанной в заголовочных файлах «fcntl.h» или «stdlib.h» и может принимать 2 значения:
O_BINARY – двоичный режим;
- текстовый режим.
По умолчанию устанавливается знчение_fmode - O_TEXT.
13.2. Доступ к файлам через поток ввода-вывода |
Связующим звеном между файлом и потоком является указатель на файл, т.е. указатель на информацию, которая определяет различные характеристики файла (имя, статус, текущая позиция и т.д.).
Указатель файла – это указатель на структуру типа FILE, шаблон которой определен в «stdio.h», который используется для ссылки на поток.
Формат объявления указателя на файл:
FILE *имя_указателя_файла;
Например, FILE *fptr;
13.2.1. Открытие потока |
Функция fopen() открывает поток и связывает с ним файл с заданным именем. Она возвращает указатель, связанный с этим файлом или NULL, если попытка открытия неудачна (например, файл отсутствует).
Формат:
FILE*fopen (char* имя_файла, char* режим_открытия);
Параметр режим_открытия определяет режим открытия файла и может принимать для текстовых файлов следующие значения:
“r” - файл открывается для чтения (файл должен существовать);
“w” - файл создается для записи (старое содержимое удаляется);
“a” - файл открывается для добавления в конец существующего файла (файл создается, если он не существует);
“r+” - файл открывается для исправления (чтения или записи) – файл должен существовать;
“w+” - файл создается для чтения и записи (старое содержимое удаляется)
“a+” - файл открывается или создается для исправления уже существующей информации и добавления новой в конец файла. Файл соэдается, если он не существует.
ПРИМЕЧАНИЕ
|
Если в любой из режимов добавить букву «b», то файл будет открыт в двоичном режиме. Например: “rb”, “w+b” и т.д. Если в любой из режимов добавить букву «t» или не добавлять вообще никакой буквы, то файл будет открыт в текстовом режиме (используется по умолчанию). Например: “rt”, “a+t”, “w+”, “a” и т.д. |
Пример: простейшее открытие файла “dat.txt”
FILE *fp;
fp=fopen (“dat.txt”,”r”);
Пример: рекомендуется использовать следующий способ открытия файла
FILE *fp;
if ((fp=fopen (“dat.txt”,”r”))==NULL)
{
printf (“Невозможно открыть файл! Программа завершается!”);
exit (1);
}
else
. . .
ПРИМЕЧАНИЕ
|
Если не указать путь к файлу, то программа должна предполагать, что текущим каталогом является тот же, в котором находится и сама программа. Для указания пути к файлу программы следует добавить в нее соответствующий код. |
Функция freopen() переназначает указатель потока на другой поток.
Формат:
FILE*freopen (char* filename, char* mode, FILE *stream);
Данная функция открывает файл с указанным именем (filename) и режимом открытия (mode), и связывает его с потоком stream. Она возвращает stream или NULL (при неудаче). Обычно эта функция используется для замены файлов, связанных с stdin, stdout и stderr.
Пример: freopen (“file.dat”,”w”,stdout); - закрывает поток для стандартного вывода и переназначает stdout файлу file.dat.
Пример: fopen.c - использование функции fopen() для открытия файлов в разных режимах
#include <stdlib.h>
#include <stdio.h>
int main( void )
{
FILE *fp;
char ch, filename[40], mode[4];
while (1)
{
printf("\nEnter a filename: ");
gets(filename);
printf("\nEnter a mode (max 3 characters): ");
gets(mode);
if ( (fp = fopen( filename, mode )) != NULL )
{
printf("\nSuccessful opening %s in mode %s.\n",
filename, mode);
fclose(fp);
puts("Enter x to exit, any other to continue.");
if ( (ch = getc(stdin)) == 'x')
break;
else
continue;
}
else
{
fprintf(stderr, "\nError opening file %s in mode %s.\n",
filename, mode);
puts("Enter x to exit, any other to try again.");
if ( (ch = getc(stdin)) == 'x')
break;
else
continue;
}
}
return 0;
}
Результат:
Enter a file name: junk.txt
Enter a mode (max 3 characters) : w
Successful opening junk.txt in mode w.
Enter x to exit, any other to continue.
J
Enter a file name: morejunk.txt
Enter a mode (max 3 characters) : r
Error opening morejunk.txt in mode r.
Enter x to exit, any other to continue.
x
13.2.2. Закрытие потока |
Функция fclose() закрывает отдельный поток, а fcloseall() –закрывает все открытые потоки, за исключением стандартных.
Формат:
int fclose (FILE *fp);
int fcloseall ();
Эти две функции возвращают нуль – в случае успеха и EOF - в случае ошибки, где EOF (End Of File) – конец файла. При ошибке все данные из буфера считываются в файл. Если поток явно не закрывается, то он будет закрыт автоматически при выходе из программы.
ВНИМАНИЕ
|
Одновременно может быть открыто не более 20 потоков. |
Функция remove() уничтожает заданный файл.
Формат:
int remove (char *имя_файла);
Возвращает 0 в случае успешного удаления файла.
СОВЕТ
|
Рекомендуем, чтобы программа всегда спрашивала пользователя, действительно ли он хочет удалить файл. |
РЕКОМЕНДУЕТСЯ
|
Откройте файл, прежде чем пытаться читать из него данные или записывать их. Используйте операцию sizeof() при вызове функций fwrite() и fread(). Закрывайте все открытые файлы по окончанию работы с ними. |
НЕ РЕКОМЕНДУЕТСЯ
|
Не предполагайте, что операции с файлами заканчиваются успешно. Всегда проверяйте, как завершились операции открытия, чтения, записи файлов. Не злоупотребляйте функцией fcloseall(), если у вас нет причин закрывать сразу все потоки. |
Пример: remove.c - использование функции remove() для удаления файла с диска
#include <stdio.h>
int main( void )
{
char filename[80];
printf("Enter the filename to delete: ");
gets(filename);
if ( remove(filename) == 0)
printf("The file %s has been deleted.\n", filename);
else
fprintf(stderr, "Error deleting the file %s.\n", filename);
return 0;
}
Результат:
Enter the filename to delete: *.bak
Error deleting the file *.bak
Результат:
Enter the filename to delete: remove.bak
The file remove.bak has been deleted.
13.3. Функции потокового ввода-вывода |
После открытия файла программа может выполнять файловый ввод-вывод.
Функции потокового ввода-вывода делятся на 4 группы:
Функции посимвольного ввода-вывода
Функции построчного ввода-вывода
Функции блокового ввода-вывода
Функции форматированного ввода-вывода
Таблица 2
Функции, использующие стандартные потоки |
Функции, требующие Указания имени потока |
Назначение |
printf() |
fprintf() |
Форматированный вывод |
vprintf() |
vfprintf() |
Форматированный вывод с переменным списком параметров |
|
||
puts() |
fputs() |
Вывод строк |
putchar() |
putc(), fputc() |
Вывод символов |
scanf() |
fscanf() |
Форматированный вывод |
vscanf() |
vfscanf() |
Форматированный вывод с переменным списком параметров |
|
||
gets() |
fgets() |
Ввод строк |
getchar() |
getc(), fgetc() |
Ввод символов |
perror() |
|
Ввод строк в stderr |
Пример: stream.c - простой пример работы со стандартными потоками
#include <stdio.h>
int main( void )
{
char buffer[256];
// ввод строки и немедленный ее вывод
puts(gets(buffer));
return 0;
}
13.3.1. Функции посимвольного ввода-вывода |
Функции посимвольного ввода-вывода – это функции, за одно обращение к которым, переносится только один символ.
Функции посимвольного ввода:
int fgetc (FILE *имя_указателя); - возвращает символ из открытого файла, описываемого переменной типа FILE, на которую указывает указатель имя_указателя.
int getc (FILE *имя_указателя); - аналогична предыдущей функции, но является макроопределением.
int fgetchar (void); - возвращает символ из файла стандартного ввода stdin.
int getchar (void); - аналогична предыдущей функции, но является макроопределением.
int ungetc (int символ, FILE *имя_указателя); - возвращает символ (его ASCII-код) назад в поток, на который ссылается указатель имя_указателя. Следующая операция чтения из этого потока вернет тот же символ.
Функции посимвольного ввода в случае успеха возвращают прочитанный символ (его ASCII-код), Преобразованный в int а при ошибке – EOF.
РЕКОМЕНДУЕТСЯ
|
Уясните себе разницу между вводом с буферизацией и без нее. Помните, что при вводе символов некоторые функции отображают вводимые символы на экране, а некоторые – нет. |
НЕ РЕКОМЕНДУЕТСЯ
|
Не используйте функции не определенные в стандарте ANSI, если программа должна быть переносимой из одной среды в другую. Не ожидайте от нестандартных функций одинакового поведения в средах различных компиляторов. |
Пример: getchar1.c – использование функции getchar()
#include <stdio.h>
int main( void )
{
int ch;
while ((ch = getchar()) != '\n')
putchar(ch);
return 0;
}
Результат:
Напечатанный пользователем текст.
Напечатанный пользователем текст.
Пример: getchar2.c – ввод целых строк текста с помощью функции getchar()
#include <stdio.h>
#define MAX 80
int main( void )
{
char ch, buffer[MAX+1];
int x = 0;
while ((ch = getchar()) != '\n' && x < MAX)
buffer[x++] = ch;
buffer[x] = '\0';
printf("%s\n", buffer);
return 0;
}
Результат:
Это строка
Это строка
ПРИМЕЧАНИЕ
|
Существуют еще две функции getch() и getche(), не определенные стандартом ANSI. |
Функция getch(), которая получает следующий символ из потока stdin. Она вводит символ без буферизации и без дублирования его на экране. Она не определена в стандарте ANSI.
ВНИМАНИЕ
|
В приведенном ниже примере используется функция getch(), не определенная в стандарте ANSI. Соблюдайте осторожность при работе с такими функциями, поскольку не все компиляторы гарантируют их поддержку. Если при компиляции приведенной программы появляется сообщение об ошибке, то вероятная причина этого – отсутствие в нем поддержки функции getch(). Так, например, компиляторы от Symantec и Borlandее поддерживают, а от Microsoft – аналогичную функцию _getch() |
Пример: getch1.c - использование функции getch()
#include <stdio.h>
#include <conio.h>
int main( void )
{
int ch;
while ((ch = getch()) != '\n')
putchar(ch);
return 0;
}
Результат: Тестируем функцию getch()
Функция getche() практически ничем не отличается от getch(), с тем единственным отличием, что функция getche() выводит введенный символ в поток stdout.
ПРИМЕЧАНИЕ
|
Функция getche() не определенна стандартом ANSI, но многие компиляторы ее поддерживают. |
Для того чтобы прочитать текстовый файл можно использовать одну из следующих конструкций.
Пример1:
int ch;
FILE *fp;
ch=fgetc (fp);
while (ch!=EOF)
{
ch=fgetc (fp);
. . .
}
Пример2:
int ch;
FILE *fp;
while ((ch=fgetc(fp))!=EOF)
{
. . .
}
Функции посимвольного вывода:
Функции посимвольного вывода помещают символ (его ASCII-код) в открытый поток вывода. Символ преобразуется в char. В случае ошибки или достижения конца файла функции возвращают EOF, в случае успеха – выведенный символ.
int fputc (int символ, FILE *имя_указателя); - помещает символ в открытый файл, который описывается переменной типа FILE, на которую ссылается указатель имя_указателя.
int putc (int символ, FILE *имя_указателя); - аналогична предыдущей функции, но является макроопределением.
int fputchar (int символ); - помещает символ в открытый файл stdout.
int putchar (int символ); - аналогична предыдущей функции, но является макроопределением.
Пример: putchar1.c - демонстрация функции putchar()
#include <stdio.h>
int main( void )
{
int count;
for (count = 14; count < 128; )
putchar(count++);
return 0;
}
Пример: putchar2.c - отображение строки с помощью функции putchar()
#include <stdio.h>
#define MAXSTRING 80
char message[] = "Displayed with putchar().";
int main( void )
{
int count;
for (count = 0; count < MAXSTRING; count++)
{
if (message[count] == '\0')
{
putchar('\n');
break;
}
else
putchar(message[count]);
}
return 0;
}
Результат: Displayed with putchar().
Когда считывается двоичный файл, то определить наличие конца файла таким образом нельзя. Для этого используется функция feof().
Формат:
int feof (FILE *имя_указателя);
Данная функция возвращает ненулевое значение, если конец файла достигнут и 0 - в противном случае.
Конструкция while(!feof(fp))
{ ch=getc(fp); …….} читает файл, открытый в любом режиме до конца.
Пример: программы, котораяоткрывает файл с именем dat для чтения, файл с именем out – для записи, читает содержимое файла dat, преобразует малые латинские буквы в большие и записывает результат в файл out.
#include <stdio.h>
#include <stdlib.h>
main ()
{
int c;
FILE *p, *q;
p=fopen (“dat”,”r”);
q=fopen (“out”,”w”);
if (!p)
{
printf (“Невозможно открыть файл dat! Программа завершается!”);
exit (1);
}
if (!q)
{
printf (“Невозможно открыть файл out! Программа завершается!”);
exit (1);
}
while ((c=fgetc(p))!=EOF)
{
if (c>=’a’ && c<=’z’) c+=’A’-‘a’;
fputc (c, q);
}
close (p);
close (q);
}
Результат: содержимое файла «dat» будет записано в файл «out» с заменой всех строчных букв на прописные (заглавные).
РЕКОМЕНДУЕТСЯ
|
Используйте функцию feof() для проверки окончания файла при работе с двоичными файлами. |
НЕ РЕКОМЕНДУЕТСЯ
|
Не считывайте данные из области памяти после конца или до начала файла. Чтобы избежать этого, проверяйте положение указателя. Не используйте константу EOF при работе с двоичными файлами. |
Пример: feof.c - использование функции feof() для обнаружения конца файла
#include <stdlib.h>
#include <stdio.h>
#define BUFSIZE 100
int main( void )
{
char buf[BUFSIZE];
char filename[60];
FILE *fp;
puts("Enter name of text file to display: ");
gets(filename);
if ( (fp = fopen(filename, "r")) == NULL)
{
fprintf(stderr, "Error opening file.");
exit(1);
}
while ( !feof(fp) )
{
fgets(buf, BUFSIZE, fp);
printf("%s",buf);
}
fclose(fp);
return 0;
}
Результат:
Enter name of text file to display:
hello.txt
Hello World!
13.3.2. Функции построчного ввода-вывода |
Функции построчного ввода-вывода – это функции, за одно обращение к которым, переносится целая строка символов.
Функции построчного ввода в случае успеха возвращают указатель на прочитанную строку символов, в случае ошибки или достижения условия конца файла функции возвращают NULL.