Void main(void)
{
#ifdef TRACE
puts("функция main\n");
#endif
getline();
}
void getline(void)
{
#ifdef TRACE
puts("функция getline");
#else
puts("hello!");
#endif
return;
}
/* текст программы после обработки препроцессором, если */
/* есть #define TRACE нет #define TRACE */
/* содержимое файла stdio.h */ /* содержимое stdio.h */
void getline(void); void getline(void);
void main(void) void main(void)
{ {
puts("функция main\n"); getline();
getline(); }
} void getline(void)
void getline(void) {
{ puts("hello!");
puts("функция getline"); return;
return; }
}
Пример 9.
/* в зависимости от значения MAX получаем три разных загрузочных модуля (генератор программ) текст программы до обработки препроцессором */
#define MAX 3
#include<stdio.h>
Void main(void)
{
#if MAX==5
puts("MAX=5");
#elif MAX==3
puts("MAX=3");
#else
puts("MAX другое");
#endif
}
/* в исходном модуле */
/* MAX равно 3 MAX равно 5 MAX равно 10 */
/* текст программы после обработки препроцессором */
/* содержимое файла stdio.h */
void main(void) void main(void) void main(void)
{ { {
puts("MAX=3"); puts("MAX=5"); puts("MAX другое");
} } }
Примечание. Текст любого системного заголовочного файла может служить примером использования условной компиляции.
ФУНКЦИИ ВВОДА И ВЫВОДА ВЕРХНЕГО УРОВНЯ
Функции ввода и вывода в стандартной библиотеке Си позволяют читать данные с клавиатуры, диска или порта ввода-вывода и выводить результат на терминал, диск или порт ввода-вывода.
Функции ввода/вывода делятся на группы:
1. ввод/вывод верхнего уровня
2. ввод/вывод системного уровня
3. ввод/вывод для консольного терминала
4. ввод/вывод через порт.
Для пользователя файл, открытый на верхнем уровне, представляется как последовательность считываемых или записываемых байтов. Чтобы отразить эту особенность, используется понятие "поток" (stream).
Функции ввода/вывода работают с буферизацией. Обмен информацией происходит не между программой и файлом, а между программой и буфером, расположенным в оперативной памяти.
Буферизация ускоряет работу программы из-за уменьшения количества обращений к внешним устройствам.
Чтобы получить доступ к файлу, необходимо связать его с соответствующей структурой типа FILE, объявленной в stdio.h и содержащей разнообразную информацию о файле. Эта операция называется открытием файла и осуществляется с помощью функции fopen, которая в качестве результата возвращает указатель на структуру FILE - указатель файла. Указатель файла в дальнейшем используется функциями для ввода/вывода информации в файл.
Указатель файла позиционируется в соответствии с открытием потока: если поток открыт для чтения - на начало файла, если для дозаписи - на конец файла. Указатель файла изменяется в соответствии с операцией записи или чтения.
После окончания ввода/вывода поток необходимо закрыть с помощью функции fclose или fcloseall, после чего указатель файла можно использовать для последующего открытия файла.
Потоки бывают текстовые или двоичные (по умолчанию - текстовые). При вводе текстового потока система Турбо Си преобразует символы возврат коретки и перевод строки в символ перевода строки, а при выводе текстового потока в среду операционной системы происходит обратное преобразование.
При вводе/выводе двоичного потока никаких преобразований не происходит.
При выполнении любой программы автоматически открываются пять стандартных потоков, имеющих следующие указатели файлов: stdin - поток ввода, stdout - поток вывода, stderr - поток вывода для сообщений об ошибках ( для вывода в поток stderr используется функция perror ), stdaux - последовательный порт и stdprn - устройство печати. Первые три потока по умолчанию связываются с консольным терминалом. Имена указателей стандартных потоков ввода/вывода могут указываться в функциях ввода/вывода.
Для переопределения стандартных потоков используется функция freopen.
Прототипы функций ввода/вывода описаны в stdio.h, и там же объявлен ряд макроопределений, в том числе NULL - нулевой байт, EOF - символ конца файла, BUFSIZ - размер буфера.
Далее приводятся описания прототипов функций с кратким пояснением.
Доступ к файлу
FILE * fopen(char *pathname, char *type);
функция открывает файл (имя задает первый аргумент) для
(второй аргумент):
"r" - чтения существующего файла
"w" - записи (перезаписи) файла
"a" - записи (дозаписи) файла
"r+" - чтения и записи существующего файла
"w+" - чтения и записи (перезаписи) файла
"a+" - чтения и записи (дозаписи) файла.
В конец строки записывается t - текстовый режим или b - дво-
ичный режим. Например: "rt" , "w+b".
Возврат при ошибке: NULL.
Закрытие потока
int fclose(FILE *stream);
int fcloseall(void);
Возврат при ошибке: EOF.
Конец файла
int feof(FILE *stream);
Возврат: 0 - не конец файла, иначе - конец файла.
Данные в файле могут быть организованы как:
1. непрерывный поток символов
2. последовательность строк переменной длины
3. форматированные поля, разделенные определенным символом
4. последовательность записей фиксированной длины.
В соответствии с этим разделим функции ввода/вывода верхнего уровня на группы.
Ввод/вывод символа
int getchar(void); - ввод из stdin
int putchar(int); - вывод в stdout
int fgetc(FILE *stream); - ввод из потока
int fputc(int c, FILE *stream); - вывод в поток
int getc(FILE *stream); - ввод из потока
int putc(int c,FILE *stream); - вывод в поток
int ungetc(char c, FILE *stream); - возврат введенного символа в поток.
Возврат при ошибке: EOF.
Построчный ввод/вывод
char * fgets(char *s,int n, FILE *stream); - cтрока из входного потока помещается в s (не более n-1 символа).
char * gets(char *s); - строка из stdin помещается в s.Возврат при ошибке: NULL.
int fputs(char *s, FILE *stream); - cтрока помещается в вы ходной поток.
int puts(char *s); - cтрока помещается в stdout.
Возврат при ошибке: EOF.
Форматный ввод/вывод
int fprintf(FILE *stream,<строка формата>,<список имен>); форматный вывод в поток.
int fscanf(FILE *stream,<строка формата>,<список адресов>); форматный ввод из потока.
int printf(<строка формата>,<список имен>); форматный вывод в stdout.
int scanf(<строка формата>,<список адресов>); форматный ввод из stdin. Ввод/вывод записей
int fread(void *bufer, int size, int count, FILE *stream); функция читает count элементов длины size из входного потока и помещает в bufer.
int fwrite(char *bufer, int size, int count, FILE *stream); функция дописывает count элементов длины size из области bufer в поток. Как правило, эти функции используются для ввода/вывода массивов и структур.
Внутреннее преобразование данных
int sprintf(char *s,<строка формата>,<список имен> ); вывод данных в строку.
int sscanf(char *s,<строка формата>,<список адресов>); ввод из строки по адресам.
Возврат при ошибке: EOF.
Прямой доступ к файлу
int fseek(FILE *stream, long offset, int origin); - функция перемещает указатель файла на offset позиций относительно начала файла, если origin равно 0, текущей позиции, если origin равно 1, конца файла, если origin равно 2. Если значение offset положительно - движение вперед, иначедвижение назад. Возврат при ошибке: не нуль.
Текущая позиция в файле
long ftell(FILE *stream); - возвращает номер текущей позиции от начала файла.
Пример 1.
/* создание копии autoexec.bat в autoexec.bak */
#include<stdio.h>
void main (void)
{
/* указатели входного и выходного потоков */
FILE *in, *out;
/* открытие входного потока */
if((in=fopen("\\autoexec.bat","rt"))==NULL)
{
fprintf(stderr, "cannot open input file\n");
exit(1);
}
/* открытие выходного потока */
if((out=fopen("autoexec.bak","wt"))==NULL)
{
fprintf(stderr, "cannot open output file\n");
exit(1);
}
/* символ из входного потока записать в выходной */
while(!feof(in))
fputc(fgetc(in), out);
/* закрытие потоков */
fclose(in);
fclose(out);
}
Пример 2.
/* внутреннее преобразование данных - из строки в float */
#include<stdio.h>
void main (void)
{
char s[20];
float chislo;
puts("\nввод числа");
gets(s);
sscanf(s,"%f",&chislo);
printf("полученное число %f\n", chislo);
}
Пример 3.
/* прямой доступ к файлу - печать 21-го символа из ес.bat */
/* в результате печатается n */
#include<stdio.h>
void main (void)
{
FILE *in, *out;
if((in=fopen("ec.bat","rt"))==NULL)
{
fprintf(stderr, "cannot open input file\n");
exit(1);
}
/* сместить указатель файла на 20 позиций от начала файла */
fseek(in,20L,0);
fprintf(stdout,"21-ый символ равен %c\n", fgetc(in));
}
/* содержимое ec.bat */
rrrrrrrrrrrrrrrrrrrdnyyyy
/* ЛАБОРАТОРНАЯ РАБОТА 3 (IN_OUT1)
ФУНКЦИИ ВВОДА И ВЫВОДА СИСТЕМНОГО УРОВНЯ
Все функции ввода/вывода верхнего уровня построены на базе более примитивных функций - функций ввода/вывода системного уровня. Наличие большого количества функций ввода/вывода верхнего уровня является их достоинством, а также недостатком, поскольку использование в одной программе нескольких функций ведет к расходу большего количества памяти. Когда применение функций ввода/вывода верхнего уровня не приносит особых преимуществ программе, можно использовать функции ввода/вывода системного уровня.
Для использования функций ввода/вывода системного уровня необходимо подключать следующие заголовочные файлы:
io.h , содержащий прототипы функций,
fcntl.h, содержащий макроопределения,
sys/stat.h, содержащий макроопределения (для open).
Для получения доступа к файлу используется функция open, которая в качестве результата возвращает неотрицательное целое значение, - дескриптор файла (handle) - которое используется при вызовах функций ввода/вывода. После окончания ввода/вывода файл необходимо закрыть с помощью функции close, после чего дескриптор файла можно использовать для последующего открытия файла.
Файлы могут быть открыты в двоичном или текстовом режиме.
При выполнении любой программы автоматически открываются пять стандартных файлов, имеющих следующие дескрипторы файлов: 0 - файл ввода, 1 - файл вывода, 2 - файл для сообщений об ошибках, 3 - последовательный порт, 4 - устройство печати. Для переопределения дескрипторов используется функция dup.
Далее приводятся описания прототипов функций с кратким пояснением.
Доступ к файлу
int open(const char *path, int access [, int permiss]);
функция открывает файл с указанным именем и готовит его в соответствии с параметром access:
O_RDONLY - для чтения
O_WRONLY - для записи
O_RDWR - для чтения и записи
Остальные параметры в сочетании (используя |):
O_APPEND - дозапись в конец файла
O_CREAT - создать файл, доступ в соотв. permiss
O_EXCL - только с O_CREAT,ошибка,если файл существует
O_TRUNC - файл усекается до 0,если существ,доступ записи
O_BINARY - двоичный режим
O_TEXT - текстовый режим
Если используется O_CREAT, permiss принимает значения: (используется заголовочный файл sys\stat.h)
S_IWRITE - запись
S_IREAD - чтение
S_IREAD | S_IWRITE - запись и чтение
Возврат при ошибке: -1.
Создать файл
int creat(const char *path, int amode);
Функция (устаревшая) создает файл или усекает до 0, если есть доступ к записи. Для amode см выше параметр permiss. Возврат при ошибке: -1.
Режим чтения файла
int setmode (int handle, int amode);
Для открытого файла с известным дескриптором устанавливается режим чтения файла (параметр amode):
O_BINARY - двоичный O_TEXT - текстовый. Возврат при ошибке: -1.
Закрытие файла
int close (int handle); Функция закрывает файл с дескриптором handle.
Конец файла
int eof (int handle);
Возврат: 0 - не конец файла, 1 - конец файла, -1 - ошибка.
Чтение из файла
int read (int handle, void *buf, unsigned len); Функция считывает len байтов из файла с дескриптором handle в буфер buf. После чтения указатель продвигается на число прочитанных байтов. Возвращает число прочитанных байтов (0 - конец файла). Возврат при ошибке: -1.
Запись в файл
int write (int handle, void *buf, unsigned len);
Функция записывает len байтов из буфера buf в файл. Возвращает число записанных байтов. Возврат при ошибке: -1.
Прямой доступ к файлу
long lseek (int handle, long offset, int fromwhere);
Функция перемещает указатель файла на offset байтов относительно fromwhere:
SEEK_SET - начала файла
SEEK_CUR - текущей позиции указателя
SEEK_END - конца файла.
Возвращает смещение относительно начала файла. Возврат при ошибке: -1L.
Текущая позиция в файле
long tell (int handle);
Функция возвращает номер текущей позиции файла относительно начала файла.
Возврат при ошибке: -1L.
Пример 1. Программа переписывает содержимое файла \autoexec.bat
в utoexec.bak.
#include<io.h>
#include<fcntl.h>
#include<sys\stat.h>
#include<stdlib.h>
#define BUFSIZE 512
void main (void)
{
int in, out; /* описание дескриптеров файлов */
int n;
char bufer[BUFSIZE]; /* буфер для ввода/вывода */
/* открыть файл для чтения */
if((in=open("\\autoexec.bat",O_RDONLY))==-1)
{
/* вывод в файл вывода сообщений об ошибках */
write(2, "cannot open input file\n",25);
exit(1);
}
/* открыть файл для чтения и записи. Если он не существует, создать с возможностью записи, если существует-перезаписать*/
if((out=open("utoexec.bak",O_CREAT|O_RDWR|O_TRUNC,S_IWRITE))==-1)
{
perror("cannot open output file\n");
exit(1);
}
/* чтение и запись идет из буфера по 512 байт */
while((n=read(in,bufer,BUFSIZE))>0)
if(write(out,bufer,n)!=n)
perror("write error");
close(in);
close(out);
}
*/
/* ЛАБОРАТОРНАЯ РАБОТА N8 (VID_MEM)
ПРЯМОЙ ДОСТУП К ВИДЕОПАМЯТИ
РАБОТА В ТЕКСТОВОМ РЕЖИМЕ
Работа видеоадаптеров
Существуют три основных типа адаптеров: монохромный, цветной графический (CGA) и усовершенствованный графический (EGA). Адаптеры могут иметь несколько режимов работы; мы будем иметь в виду три режима, использующие 80-символьный текст: режим 2 или 3 (для CGA, EGA) ( режим 7 (для монохромного адаптера). Текст в этих режимах выводится в 80 столбцов и 25 строк.
Адаптер дисплея персонального компьютера обладает достаточной памятью для хранения информации, изображенной на экране.
Имеется три способа доступа к видеоадаптеру: через прерывания DOS (самый медленный), через процедуры BIOS, прямой доступ к видеопамяти (самый быстрый, но требующий большей работы от программиста).
Средствами языка Си возможно получить доступ непосредственно к этой памяти для ввода-вывода информации.
Прямой доступ к видеопамяти
Каждый символ, изображенный на экране, требует два байта видеопамяти: первый байт содержит символ, второй - его атрибуты.
Байт атрибутов для цветного монитора определен следующим образом:
1. номера битов в байте: 7 6 5 4 3 2 1 0
2. роль битов: 0 - голубой символ
1 - зеленый символ
2 - красный символ
3 - интенсивность изображения символа
4 - голубой фон
5 - зеленый фон
6 - красный фон
7 - мерцание символа
3. значения битов: 0 - атрибут выключен, 1 - включен. Значение байта атрибута 0x7 воспроизводит белый символ на черном фоне, значение 0х70 - черный символ на белом фоне.
Для записи и хранения информации, изображенной на экране с размерами 80х25, требуется не менее 4000 байтов памяти - страница видеопамяти. В зависимости от памяти адаптер может сохранять информацию на нескольких страницах видеопамяти, но активной
может быть только одна из них - информация именно активной страницы видеопамяти изображается на экране. По умолчанию активна нулевая страница видеопамяти. Изменить номер активной видеостраницы можно, например, с помощью функции системного прерывания int86.
Адреса нулевой страницы видеопамяти для монохромного монитора 0хВ0000000, для цветного монитора - 0хВ8000000.
Изображение символа на странице видеопамяти
Чтение и запись в видеопамять производится с помощью использования дальних указателей на видеопамять ( явно, с помощью ключевого слова far, или используя модель памяти, где все указатели по умолчанию дальние). Для определения адреса отдельного символа, изображенного на соответствующей видеостранице, используется формула адрес = адрес_видеостраницы + y*160 + x*2, где x - горизонтальная, а y - вертикальная координаты символа на экране. При выбранном режиме работы 0<= x <80, 0<= y <25.
Адрес атрибута этого же символа на единицу больше.
Пример 1. C помощью прямого доступа к нулевой видеостранице создать на экране приятный фон.
*/
#include <stdio.h>
