11. Файлы

11.1. Потоки. Стандартные потоки ввода-вывода

    Общая схема работы с дисковыми файлами в Си мало чем отличается от схемы работы с файлами в Паскале. Сначала требуется описать специальную "файловую переменную", далее установить соответствие между ней и файлом на диске, открыть файл (для чтения, записи, дозаписи и т.п.), поработать с файлом и закрыть его. Так что все довольно просто. Тем не менее работа с файлами в Си имеет свои особенности, на которых мы остановимся. Прежде всего попробуем разобраться с понятием, которое часто используется в литературе по Си и связано с реализацией ввода-вывода в этом языке. Как обычно говорят, в Си используется "потоковый" ввод-вывод. Вы, возможно, будете удивлены, но в Паскале тоже используется потоковый ввод-вывод, но в книгах по Паскалю внимания самому этому термину уделяется гораздо меньше. Если не вдаваться в тонкости реализации языка, то поток — это на самом деле та самая переменная, которая используется для связи с файлом (более точно — с файлом или устройством). Имеется и несколько предопределенных потоков (предопределенных файловых переменных), связанных со стандартными устройствами. Так, поток stdin изначально связан со стандартным устройством ввода (обычно клавиатурой), а поток stdout — со стандартным устройством вывода (обычно терминалом). Имеется также поток stderr для вывода сообщений об ошибках. Он также изначально связан со стандартным устройством вывода (терминалом). Чтобы не сложилось впечатление, что механизм потокового ввода-вывода является исключительно особенностью Си, ниже приведены практически эквивалентные примеры на Си и Паскале.

Вот пример на Паскале, демонстрирующий, что в Паскале также имеется стандартный поток вывода output, изначально связанный с терминалом.

begin
    writein (output, 'Привет, Мир!')
end.

Аналогичный пример на Си будет выглядеть несколько иначе. Дело в том, что ряд функций вывода (например, printf) всегда работает именно со стандартным устройством вывода. Для работы с потоками, отличными от стандартных, имеются другие функции. (В данном случае мы все равно используем стандартный поток, но задать его хотим не "по умолчанию", а явно.) Поэтому вместо printf надо использовать функцию fprintf, первым параметром которой является поток. Поясним сказанное примером:

#include <stdio.h>
void main(void)
  { fprintf(stdout, "Привет, Мир!");}

11.2. Текстовые и двоичные потоки

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

Так сложилось, что файлы делятся на текстовые и нетекстовые (последние иногда называют двоичными, или бинарными), файл, содержащий программу на Си, — текстовый; файл, который вы можете создать, используя, например, встроенный редактор Norton Commander (FAR или кому что нравится), — тоже текстовый. А вот файл, содержащий, например, рисунок в формате JPEG (да и в любом другом графическом формате), — двоичный. Текстовые файлы отличаются от двоичных двумя особенностями: во-первых, они делятся на строки, каждая из которых заканчивается "переводом строки", состоящим из двух символов с кодами 0x0D 0x0A; во-вторых, текстовые файлы заканчиваются "признаком конца файла" — символом с кодом 0х1А (точнее, должны заканчиваться, это условие соблюдается не всегда).

При чтении текстового файла (потока) функции Си преобразуют "признак конца строки", т.е. последовательность символов 0x0D 0x0A, в один символ 0x0A (' \п' ), а "признак конца файла (потока)" — в значение EOF (End Of File). Константа EOF определена в заголовочном файле stdio.h и обычно равна —1.

При чтении двоичных потоков никаких преобразований не производится.

То, с каким потоком мы собираемся работать — текстовым или двоичным, указывается при его открытии. Один из способов открытия потока —использование функции fopen (соответствующие примеры имеются ниже). Тип потока указывается в строке параметров, которая является вторым аргументом этой функции. Пример: f=fopen ("test.ext", " rt"} ; — открытие текстового файла test.ext для чтения и связывание его с файловой переменной f. На то, что файл открывается как текстовый, указывает буква "t" в строке "rt". Чтобы открыть этот файл как бинарный, надо использовать букву "b": f=fopen ("test.ext", "rb") ;

11.3. Перенаправление ввода/вывода

Вполне можно организовать ввод данных из файла и вывод в файл безо всяких файловых переменных и функций работы с файлами. Для этого можно воспользоваться механизмом перенаправления потоков, который имеется в операционной системе. Рассмотрим следующую программу:

#include <stdio.h>
  void main(void)
  { int i;
    for (i=32;i<=255;i++)
     { if (! (i%8)) printf("\n") ;
       printf("%3d - %c ",i,i);
     }
   }

Если мы просто запустим данную программу на выполнение, то результат (таблица символов и их кодов) будет выведен на экран. Происходит это потому, что, как уже было отмечено, стандартный поток вывода stdout, с которым работает printf, — это экран. Но если переназначить данный поток, т.е. запустить исполняемый файл с нашей программой (пусть он называется ascii.exe) следующим образом:

ascii.exe > ascii.txt

— то мы получим ту же самую таблицу в файле ascii.txt.

Точно так же можно перенаправлять и стандартный поток ввода stdin. Эту возможность особенно удобно использовать при отладке программ, содержащих достаточно много исходных данных. Так, отлаживая какую-нибудь матричную задачу, гораздо проще один раз создать текстовый файл с исходными значениями и подавать его на вход программы, чем каждый раз вводить матрицы вручную. (Перенаправление потока ввода производится посредством указания символа "<" в командной строке.)

11.4. Простые примеры

В разделе 11.3 мы получили файл ascii.txt посредством перенаправления стандартного потока вывода. Приведем теперь пример того, как сделать то же самое, используя средства работы с файлами.

#include <stdio.h>
void main(void)
   { int i;
    FILE *f=fopen("ascii.txt","wt") ;
    for (i=32;i<=255;i++)
      { if (!(i%8)) fprintf(f,"\n");
         fprintf(f,"%3d - %c ",i,i);
      }
   fclose(f);
  }

Из этого примера видно, что файловая переменная описывается как указатель на тип FILE (на самом деле это структура, описанная в stdio.h). Связывание файловой переменной с дисковым файлом и открытие файла выполняется функцией fopen. Первым параметром этой функции является имя файла, а вторым — строка формата, в которой могут использоваться следующие символы:

г — открыть для чтения;
w — открыть для записи;
а — открыть для дозаписи;
t — текстовый поток;
b — двоичный (бинарный) поток.

Если тип потока явно не указан, то поток считается текстовым.

После r, w и a можно добавить " + ", в этом случае файл открывается для произвольного доступа (чтения и записи).

11.5. Взгляд на файл через окно

Важным понятием, о котором необходимо иметь представление при работе с файлами, является так называемый файловый указатель. Любой файл, каким бы он ни был и как бы не обрабатывался, представляет собой просто последовательность байтов, файловый указатель в каждый момент времени указывает на некоторый байт файла, начиная с которого будет выполняться следующая операция. Любая операция с файлом каким-то образом изменяет положение файлового указателя. Например, при открытии файла для чтения ("г" ) файловый указатель устанавливается на первый байт файла (более точно — файловый указатель, являющийся обычной длинной целой переменной, принимает значение 0). То же самое происходит и при открытии файла для записи ("w" ), только в этом случае старое содержимое файла (если оно было) теряется. А вот при открытии файла для дозаписи ("а" ) файловый указатель устанавливается на конец файла. При чтении данных из файла указатель сдвигается "вперед" на количество байт, которое занимают прочитанные данные. При записи в файл — тоже вперед на количество байт, которое занимают записанные данные. Фактически можно представлять себе файл как ленту (правда, конечную) в машине Тьюринга, а файловый указатель как "каретку" этой машины.

В ряде языков программирования различают файлы "последовательного" и "прямого" доступа. Говоря кратко, различие между ними заключается в том, что в файлах последовательного доступа файловый указатель можно перемещать только "вперед" (от начала к концу файла), а в файлах прямого доступа его можно помещать в любое место файла (в частности, при необходимости возвращаться "назад"). В Си, вообще говоря, все файлы являются файлами прямого доступа, хотя работать с текстовыми файлами как с файлами прямого доступа неудобно. Поэтому пример перемещения файлового указателя (посредством функции fseek) приведен ниже в программе показа картинки, записанной в формате PCX (соответствующий файл является бинарным).

11.6. Пример работы с текстовым фйлом

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

Пусть у нас имеется матрица, для определенности, четных размерностей. Размерности эти должны быть таковы, чтобы их произведение (количество клеток матрицы) заведомо превышало длину строки шифруемого файла. (Вообще говоря, ясно, что строки в текстовом файле могут иметь практически неограниченную длину. Но если предположить, что мы имеем дело с "обычными" файлами, то длины строк в них редко превышают... ну, скажем, 128 символов.) Так что матрицы 12 х 14 в практических задачах хватает, как говорится, "за глаза" (мы специально выбрали не квадратную матрицу, чтобы продемонстрировать, что матрица вовсе не обязана быть квадратной). Для отладки мы использовали матрицу меньших размеров (она приведена ниже), и, соответственно, длину строк в файле тоже пришлось ограничить.

Итак, мы имеем матрицу N х М. (N и М — четные). Вырежем из нее ровно четверть клеток, но не просто "абы как", а таким образом, чтобы при четырех возможных положениях матрицы (получаемых различными преобразованиями симметрии) вырезанные клетки не попадали друг на друга. Формальное условие будет следующим: если мы вырезали клетку (i, j), то не можем вырезать клетки (i, M—j—1), (N—i—1.7), (N—i—1, М—j—1). Мы использовали для отладки следующую матрицу:

 

1

0

1

0

0

0

0

0

0

0

0

0

0

1

1

0

0

1

0

0

0

1

0

0

1

0

0

1

0

0

0

0

0

0

0

1

0

0

0

1

0

0

0

0

1

0

1

0


После этого возьмем строку текста, которую требуется зашифровать (обычно из нее удаляют пробелы, знаки препинания и записывают буквы в одном регистре — это затрудняет, хотя, признаться, ненамного, "взлом" шифра), и начнем последовательно вписывать ее символы в вырезанные клетки. Когда вырезанные клетки закончатся, перевернем матрицу (мы отображаем ее относительно горизонтальной оси) и продолжим вписывать буквы. Далее нам, возможно, придется еще раз или два переворачивать матрицу. Когда строка будет вписана полностью, впишем в оставшиеся пустыми клетки некоторый символ (мы вписывали звездочку) и "развернем" то, что получилось, в одну строку. Это и есть зашифрованный текст. Чтобы его расшифровать, необходимо иметь матрицу-ключ (ну а сам процесс расшифровки, как мы полагаем, очевиден читателю).

Посредством приведенной выше матрицы, последовательно расположенной следующим образом:

— текст КРАТКИЙПРАКТИЧЕСКИЙКУРСЯЗЫКАСИ 
превратился в следующий: КЗРЫИ*Ч*К**ЕААТСКК*ИСИ*ИЙЙ*П*К*****Р*УРАС*Я*К*Т*

Замечание. Зашифрованная матрица имела вид:

 

К

3

Р

Ы

И

*

Ч

*

К

*

*

Е

А

А

Т

С

К

К

*

И

С

И

*

И

Й

Й

*

П

*

К

*

*

*

*

*

Р

*

У

Р

А

С

*

Я

*

К

*

Т

*


Приведенная ниже программа имеет следующие параметры командной строки:

<имя файла с матрицей-ключом> <c[d> <имя исходного файла> <имя файла с результатом>

Все параметры являются обязательными, второй параметр определяет действие, которое необходимо произвести с исходным файлом ("с" — зашифровать, "d" — расшифровать).

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#define L 20
#define С 20
struct order {int 1,c;};
/* Функции обработки ошибок */
 void error (int);
/* Функция чтения матрицы-ключа из текстового файла */
 void readkey{char *,char [L][С],int *,int *);
/* Преобразование цифры-символа в цифру-число */
 int digit(cnart);
/* Проверка (неполная!) ключа на корректность */
void checkkey(char L][С],int,int) ;
/* Отображение матрицы относительно горизонтальной оси */
 void simhor(char L][С],int,int);
/* Отображение матрицы относительно вертикальной оси */
 void simvert(char [L][С],int,int);
/* Функция получает последовательность клеток матрицы, в которые вписываются символы */
 void genlkey(struct order *,char [L][С],int,int) ;
/* Инициализация матрицы-ключа */ 
void initmcrypt(char [L][C],int,int);
/* Шифрование строки */
void cryptstring(char *,char *,struct order *,int,int);
/* Дешифрация строки */ 
void decryptsfcring(char *,char *,struct order *,int,int);
/* Шифрование файла */ 
void cryptfile(char *,char *,struct order *,int,int);
/* Дешифрация файла */ 
void decryptfile(char *,char *,struct order *,int,int);
void swap(char *,char *) ;
void error(int n) 
   { switch (n)
        { case 0: printf("\nHe удается открыть файл с матрицей-ключом");exit(0);
           case 1: printf("\пКлюч не прошел проверку на корректность");exit(0);
           case 2: printf("\пНедостаточно параметров в командной строке");exit();
           case 3: printf("\п0шибка при открытии шифруемого файла") ; exit ();
           case 4: printf("\п0шибка при открытии файла, в который 
                                 требуется поместить зашифрованный текст");exit();
           case 5: printf("\п0шибка при открытии зашифрованного файла")/exit ();
           case 6: printf("\п0шибка при открытии файла, в который требуется 
                       поместить расшифрованный текст"); exit();
            case 7: printf("\пНеиэвестная операция (с - шифрование, d - расшифрование)");exit(0) ;
          }
       }
void readkey(char *name,char key[L][C],int *l,int *c) /* name - имя файла с матрицей-ключом key - матрица-ключ, 1 - номер последней строки, с - номер последнего столбца */ 
{ FILE *f=fopen(name,"rt");
   *1=0;
   if (!f) error(0);
   while (!feof(f))
     { fgets(key[*1],C,f);
        if (key[*l][strlen(key[*l])-l]=='\n') key[*l][strlen(key[*1])-1]=0;
        (*1)++;
      }
     *c=strlen(key[0])-1;
     (*1)--;
     fclose(f);
  }
int digit(char c) { return c-'0';}
void checkkey(char key[L][C],int l,int c)
   { int i,j;
        for (i=0;i<=l/2;i++) 
           for (j=0;j<=c/2;j++)
              if (digit(key[i][j])+digit(key[i][c-j])+digit(key[l-i][j]])+digit(key[l-i] [c-j;])!=1)
     error(1) ;
     }
void swap(char *x,char *y)
  { char t=*x;
    *x=*y;
    *y=t;
  }
void simhor(char key[L][C],int l.int c)
  { int i,j;
    for (i=0;K=l/2;i++)
     for (j=0;j<=c;j++) swap(&key[i] [j] , &key[l-i] [j] ) ;
  }
void simvert(char key[L][C],int l,int c)
  { int i,j;
      for (i=0;i<=l;i++)
        for (j=0;j<=c/2;j++) swap(&key[i][j],&key[i][c-j]) ;
  }
void printkey (char key[L][C], int l)
  { int i;
    printf("\n") ;
    for (i=0;i<=l;i++) printf("\n%s",key[i]);
  }
void genlkey(struct order *lkey,char key[L][C],int 1,int c)
  { int k=0,i,j;
      for (i=0;i<=l;i++)
         for <j=0;j<=c;j++)
            if <key[i][j]=='l') {lkey[k].l=i;lkey[k].c=j;k++;}
      simhor(key,l,с) ;
       for (i=0;i<=l;i++)
           for (j=0;j<=c;j++)
             if <key[i] [j]=='1') {lkey[k] .l=i;lkey[k] .c=j;k++; }
      simvert(key,l,c) ;
        for (i=0;i<=l;i++)
            for (j=0;j<=c;j++)
              if (key[i][j]=='1') {lkey[k].l=i;lkey[k].c=j;k++;}
       simhor (key,l,c) ;
         for (i=0;i<=l;i++)
             for (j=0;j<=c;j++)
               if (key[i][j]=='1') {lkey[k].l=i;Ikey[k].c=j;k++;}
         simvert(key,l,с) ;
   }
void initmcrypt(char mcrypt[L][C],int l,int c)
   { int i,j;
     for (i=0;i<=l;i++)
        for (j=0;j<=c;j++) mcrypt[i][j]]='*';
    }
void cryptstring(char *source,char *dest,struct order *lkey,int l,int c)
 { char mcrypt[L][C] ;
    int k=0,i,j;
    initmcrypt(mcrypt,l,с) ;
     while (*source)
      {mcrypfc[lkey[k].1][lkey[k].c]=*source; k++;source++;}
     k=0;
     for (i=0;i<=l;i++)
       for(j=0;j<=c;j++) dest[k++]=mcrypt[i] [j] ;
    dest[k]=0;
 }
void decryptstring(char *source,char *dest,struct order *lkey,int l,int c)
  { int k=0,i,j;
    char mcrypt[L][C] ;
    for (i=0;i<=l;i++)
      for (j=0;j<=c;j++) {mcrypt[i][j]=*source;source++;}
    while (mcrypt[ikey[k].1][lkey[k].c]!='*') dest[k++]=mcrypt[Ikey[k].l][lkey[k].c] ;
    dest[k]=0;
   }
void cryptfile(char *sourcefile,char *destfile,struct order *lkey,int l.int c) 
 { FILE *sf==fopen(sourcefile, "rt") , *df=fopen (destfile, "wt") ;
   char source[L*C+l],dest[L*C+l] ;
   if (!sf) error(3);
   if (!df) error(4);
   while (!feof(sf))
      { fgets(source,L*C,sf) ;
         if (source[strlen(source)-1]=='\n') source[strlen(source)-1]=0;
         cryptstring(source,dest,lkey,l, с) ;
         if (!feof(sf)) strcat(dest,"\n") ;
        fputs(dest,df) ;
      }
     fclose(sf);fclose(df) ;
  }
void decryptfile(char *sourcefile,char *destfile,struct order *lkey,int i,int c) 
  { FILE *sf=fopen(sourcefile,"rt"),*df=fopen(destfile,"wt") ;
     char source[L*C+1],dest[L*C+1] ;
     if (!sf) error(5);
     if (!df) error(6);
     while (!feof(sf))
       { fgets(source,L*C,sf);
          if (source[strlen(source)-1]=='\n') source[strlen(source)-1]=0,;
          decryptstring(source,dest,lkey,l,с);
          if (!feof(sf)) strcat(dest,"\n");
          fputs(dest,df) ;
    }
    fclose(sf);fclose(df) ;
}
void main(void)
   { char mkey[L][C] ;
      struct order lkey[L*C];
      char source[L*C+1],dest[L*C+1],dest2[L*C+1];
      int l,c;
      cirscr();
      if (_argc<5) error (2);
      readkey( argv[1],mkey,&1, &c) ;
      checkkey(mkey,l,с) ;
      genlkeу(lkey,mkey,l, с) ;
      switch (_argv[2][0] )
         { case 'c': cryptfile( argv[3], _argv[4],lkey,l,с);break;
            case 'd': decryptfile (_argv [3] , _argv [4] , lkey, l, с) ;break;
            default: error(7);
          }
  }

11.7. Примеры работы с двоичными файлами . Просмотр картинок, записанных  в форматах BMP и PCX

В качестве примеров работы с бинарными файлами приведем программы для просмотра картинок, записанных в форматах BMP и PCX. На практике мы используем эту тему для того, чтобы рассказать ребятам о различных форматах графических файлов — векторных, растровых, со сжатием и без. С рассмотрения форматов BMP и PCX мы начинаем потому, что первый является совсем простым (таким образом, это лишь учебный пример на работу с двоичными файлами), а во втором используется какое-никакое сжатие информации Поскольку не создаем профессиональный программный продукт, а учимся работать с файлами, рассматриваются только 256-цветные картинки с размерами, не превышающими 320 X 200. Ранее мы уже написали библиотеку функций для работы с режимом VGA 13h (320 х 200 х 256), поэтому здесь будут приведены лишь программы показа картинок

11.7.1. Программа показа 256-цветной картинки, записанной в формате BMP

Файл проекта для этой программы будет содержать две строчки

bmp256.с
vga256.с

Текст программы bmp256.с приводится ниже Перед тем как изучать текст программы, полезно получить общее представление о том, как устроены BMP-файлы (в частности, 256-цветные BMP-файлы)

В ВМР-файле имеются две части — так называемый заголовок, в котором хранится информация о картинке (соответствующая структура приведена ниже, правда, приходится отметить, что лишь небольшое количество полей заголовка нами будет использовано), и собственно изображение Изображение хранится по точкам, в построчной развертке, начиная с нижней строки картинки

#include "vga256.h"
#include <stdio.h>
#include <conio.h>
#include <dos.h>

typedef struct BMPHDR 
 { unsigned int bfType;
   unsigned long bfSize;
   unsigned int Reserved1;
   unsigned int Reserved2;
   unsigned long bfOffBits;
   unsigned long biSize;
   long Width,Height;
   unsigned Planes, BitCount;
   unsigned long Compression,Sizelmage;
   long XRes,YRes;
   unsigned long Ignored1,Ignored2;
   struct { unsigned char Blue, Green, Red, Reserved;} Palette[256];
 } BMPHEADER;

BMPHEADER h;

void fatalerror(int n) 
  { switch (n)
      { case 0: printf("\пНадо задать имя файла");exit(0);
         case 1: printf("\п0шибка при открытии файла");exit(0);
         case 2: printf("\п0шибка при чтении заголовка");exit(0);
       }
    }

void outfile(FILE *f)
  { unsigned int x,y,b;
     for(y=0;y<h.Height;y++)
        for (x=0;x<h.Width;x++)
        { b=fgetc(f);
           putpixel256(x,h.Height-y-l,b) ;
        }
    }

void setpal()
  {   int i;
      RGB pal;
      for(i:=0;i<256;i++)
       { pal [i].r=h.Palette[i] .Red>>2;
         pal [i].g=h.Palette [i] .Green>>2;
         pal [i].b=h.Palette[i] .Blue>>2;
       }
     setVgaDAC(pal) ;
   }

void main(void)
  { FILE *f=fopen(_argv[l],"rb") ;
     if (_argc<2) fatalerror(0) ;
     if (!f) fatalerror(1) ;
     if (1!=fread(&h,sizeof(BMPHEADER),1,f)) fatalerror(2) ;
     set256() ;
     outfile(f) ;
     setpal();
     getch() ;
     setTEXT();
     fclose(f);
}

11.7.2. Программа показа 256-цветной картинки, записанной в формате PCX

Как и в предыдущем примере, файл проекта содержит всего две строки:

рсх256.с
vga256.с

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

1 1 1 2 1 1 1 1 2 2 2 10 10 12 12 12 13 13 13 13 13 13 13 1 1 1 1 5 6 5 5 5 5

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

3 1 2 4 1 3 2 10 10 3 12 7 13 4 1 5 6 4 5

Байты, в которых записано количество повторений, выделены полужирным.

И все было бы совсем просто, если бы не... изображения с большим количеством цветов (в частности, с 256 цветами). Поясним, в чем проблема. Когда разрабатывался формат PCX, его разработчики не предполагали... что появятся изображения с количеством цветов, большим 16. И поэтому придумали следующее, как им казалось, изящное решение: если два старших бита байта равны 11 (т.е. байт больше 0хС0 ), то этот байт является байтом-повторителем (количество повторений записано в младших шести битах), в противном случае байт является просто номером цвета. Как только количество цветов в изображениях превысило 64, пришлось перед цветами с номерами, большими 63, всегда ставить байт-повторитель (даже если точка такого цвета всего одна).

Но и это еще не все. Как и в файлах формата BMP, в PCX-файлах также имеется заголовок. И в нем тоже было предусмотрено место для хранения палитры. Но... Только 16-цветной палитры! В результате 256-цветную палитру пришлось дописывать к концу PCX-файла (т.е. он состоит не из двух частей — заголовка и изображения, а из трех —-заголовка, изображения и палитры). Все это видно и из приведенной ниже программы (файл рсх25б.с).

#include "vga256.h"
#include <stdio.h>
#include <dos.h>
#include <conio.h>

/* Описание структуры для заголовка PCX-файла */
typedef struct PCXHDR
   {   unsigned char PCSID;/* 0х0А для файлов PCX */
        unsigned char Version;/* версия формата PCX */
        unsigned char Encoding;/* 1 - есть сжатие */
        unsigned char BitsPerPixel;/* Бит на пиксель */
        unsigned int LeftX,LeftY;/* Верхний левый угол */
        unsigned int RightX,RightY;/* Правый нижний угол */
        unsigned int DisplayXRes,DisplayYRes;/* Разрешение дисплея */
        unsigned char Palette[48];/* Палитра */ 
        unsigned char Reserved1;/* Резервный байт */
        unsigned char NPlanes;/* Количество плоскостей */ 
        unsigned int BytesPerRow;/* Количество байт в строке */
        unsigned int Palettelnfo;/* 1 - цветная палитра; 2 - ч/б */
        unsigned char Reserved2[58];
   } PCXHEADER;

PCX HEADER h;

/* Функция обработки ошибок */
void fatalerror(int n)
   { switch (n)
           { case 0:printf("\nНадо задать имя файла");exit (0);
              case 1:printf("\n0шибка при открытии файла");exit(0);
              case 2:printf("\n0шибка при чтении заголовка");exit(0);
            }
      }

/* Вывод картинки из файла */
void outfile(FILE *f)
   { unsigned int x,у,ImH=h.RightY-h.LeftY+1,j,i,b,repeat,l;
       for(j=0;j<ImH;j++)
        { i=0;x=0;y=j;
          while(i<h.BytesPerRow)
            { b=fgetc(f);
               if (b>0хС0) {repeat=b&0x3F;b=fgetc(f);}
                else repeat=1;
               for (l==0;l<repeat;l++) {putpixel256 (x, y, b) ; x++; i++; }
             }
       }
}

/* Чтение палитры в массив и установка всех цветов */ 
void setpal(FILE *f)
  {  int i;
     RGB pal;
     fseek(f,-768,SEEK_END);
     fread(pal,3,256,f) ;
     for(i=0;i<256;i++)
         { pal [i].r=pal[i].r>>2;
           
pal[i].g=pal[i].g>>2;
            pal[i].b=pal[i].b>>2;
          }
     setVgaDAC(pal) ;
}

void main(void) 
 { FILE *f=fopen(_argv[l],"rb");
    if (_argc<2) fatalerror (0);
    if (!f) fatalerror(1);
    if (l!=fread(&h,sizeof(PCXHEADER),1,f)) fatalerror (2);
    set256() ;
    outfiie(f) ;
    setpal(f) ;
    getch() ;
    setTEXT () ;
    fclose(f);
} TopList