7.29.

Напишите программу, печатающую тексты Си-программ на принтере. Выделяйте ключевые слова языка жирным шрифтом, строки "строка", символы 'c' и комментарии - курсивом. Шрифты для EPSON-FX совместимых принтеров (например EP-2424) переключаются такими управляющими последовательностями (ESC означает символ '\033'):

                                  ВКЛЮЧЕНИЕ    ВЫКЛЮЧЕНИЕ
    жирный шрифт     (bold)          ESC G        ESC H
    утолщенный шрифт (emphasized)    ESC E        ESC F
    курсив           (italics)       ESC 4        ESC 5
    подчеркивание    (underline)     ESC - 1      ESC - 0
    повышенное качество печати       ESC x 1      ESC x 0
             (near letter quality)     nlq         draft
    верхние индексы  (superscript)   ESC S 0      ESC T
    нижние индексы   (subscript)     ESC S 1      ESC T
    сжатый шрифт (17 букв/дюйм)      '\017'       '\022'
                     (condensed)
    двойная ширина букв              ESC W 1      ESC W 0
                     (expanded)
    пропорциональная печать          ESC p 1      ESC p 0
                     (proportional spacing)

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

            pitch (плотность печати)
    pica   (10 букв/дюйм)            ESC P
    elite  (12 букв/дюйм)            ESC M
    micron (15 букв/дюйм)            ESC g

            font  (шрифт)
    черновик (draft (Roman))         ESC k '\0'
    текст    (text  (Sans Serif))    ESC k '\1'
    курьер   (courier)               ESC k '\2'

Всюду выше 0 означает либо '0' либо '\0'; 1 означает либо '1' либо '\1'. Пример:

    printf( "This is \033Gboldface\033H word\n");

7.30.

Составьте программу вывода набора файлов на печать, начинающую каждый очередной файл с новой страницы и печатающую перед каждым файлом заголовок и номер текущей страницы. Используйте символ '\f' (form feed) для перевода листа принтера.

7.31.

Напишите программу печати текста в две колонки. Используйте буфер для формирования листа: файл читается построчно (слишком длинные строки обрубать), сначала заполняется левая половина листа (буфера), затем правая. Когда лист полностью заполнен или файл кончился - выдать лист построчно, расписать буфер пробелами (очистить лист) и повторить заполнение очередного листа. Указание: размеры листа должны передаваться как аргументы main(), для буфера используйте двумерный массив букв, память для него заказывайте динамически. Усложнение: не обрубайте, а переносите слишком длинные строки (строка может потребовать даже переноса с листа на лист).

    /* ПРОГРАММА ПЕЧАТИ В ДВЕ ПОЛОСЫ: pr.c */
    #include <stdio.h>
    #include <string.h>
    #define YES 1
    #define NO  0
    #define FORMFEED '\f'
    #define LINEFEED '\n'

    extern char *malloc(unsigned);
    extern char *strchr(char *, char);
    void untab(register char *s);
    void resetsheet( void );
    void addsheet( char *s, FILE *fpout );
    void flushsheet( FILE *fpout );
    void printline( int y, char *s, char *attr,
                    FILE *fpout );
    void doattr( register char *abuf,
                 register char *vbuf );
    void printcopy( FILE *fpin, FILE *fpout );
    void main(void);
    char *strdup (const char *s){
      char *p = malloc(strlen(s)+1); strcpy(p,s); return p;

      /* return strcpy((char *) malloc(strlen(s)+1), s); */
    }

    /* ... текст функции untab() ... */

    int Sline;       /* строка на листе            */
    int Shalf;       /* половина листа             */
    int npage;       /* номер страницы             */
    int startpage = 1;
                /* печать начиная с 1ой страницы   */
    int fline;       /* номер строки файла         */
    int topline = 0; /* смещение до начала листа   */
    int halfwidth;   /* ширина полулиста           */
    int twocolumns = YES;   /* в две колонки ?     */
    int lshift, rshift = 1; /* поля слева и справа */
    typedef unsigned short ushort;
    int COLS  = 128; /* ширина листа (букв)        */
    int LINES =  66; /* длина листа (строк)        */
    ushort *mem;     /* буфер листа                */
    #define AT(x,y)  mem[ (x) + (y) * COLS ]

    /* Выделить буфер под лист и зачистить его */
    void resetsheet ( void ){
       register x;
       if( mem == NULL ){ /* выделить память */
           if ((mem = (ushort *)
             malloc (COLS * LINES * sizeof(ushort)))
             == NULL ){
             fprintf(stderr, "Out of memory.\n"); exit(1);
           }
       }
       /* очистить */
       for( x= COLS * LINES - 1 ; x >= 0 ; x-- )
               mem[x] = ' ' & 0xFF;
       halfwidth = (twocolumns ? COLS/2 : COLS )
                   - (lshift + rshift );
       Sline = topline; Shalf = 0;
    }

    #define NEXT_HALF \
       if( twocolumns == YES && Shalf == 0 ){         \
           /* закрыть данную половину листа */        \
           Shalf = 1; /* перейти к новой половине */  \
           Sline = topline;                           \
       } else                                         \
           flushsheet(fpout)   /* напечатать лист */

    /* Записать строку в лист */
    void addsheet ( char *s, FILE *fpout )
    {
       register x, y;
       register i;
       char *rest = NULL;
       int wrap = NO;
    /* YES когда идет перенос слишком длинной строки */

       /* в какое место поместить строку? */
       x = (Shalf == 0 ? 0 : COLS/2) + lshift;
       y = Sline;

       i = 0;     /* позиция в строке s */
       while (*s) {
           if( *s == '\f' ){
               /* вынужденный form feed */
               rest = strdup( s+1 ); /* остаток строки */
               NEXT_HALF;
               if( *rest ) addsheet(rest, fpout);
               free( rest );
               return;
           }
           if( i >= halfwidth ){
           /* перенести длинную строку */
               wrap = YES;
               rest = strdup(s);
               break;
           }
           /* Обработка выделений текста */
           if( s[1] == '\b' ){
               while( s[1] == '\b' ){
                  AT(x, y) = (s[0] << 8) | (s[2] & 0xFF);
                  /* overstrike */
                  s += 2;
               }
               s++; x++; i++;
           } else {
               AT (x, y) = *s++ & 0xFF;
               x++; i++;
           }
       }
       /* Увеличить строку/половину_листа */
       Sline++;
       if (Sline == LINES) { /* полулист заполнен */
           NEXT_HALF;      }
       if( wrap && rest )  { /* дописать остаток строки */
           addsheet(rest, fpout); free(rest);
       }
    }
    int again;      /* нужна ли повторная надпечатка? */
    /* Напечатать заполненный лист */
    void flushsheet ( FILE *fpout ){
       register x, y, xlast;
       char *s, *p;
       static char outbuf[BUFSIZ], attr[BUFSIZ];
       /* attr - буфер под атрибуты выделений */
       ushort c;

       if( npage >= startpage )
         for (y = 0; y < LINES; y++) {
            /* обрезать концевые пробелы */
            for (xlast = (-1), x = COLS - 1; x >= 0; x--)
               if (AT (x, y) != ' ') { xlast = x; break; }
            again = NO; s = outbuf;  p = attr;
            for (x = 0; x <= xlast; x++){
               c = AT(x, y);
               *s++ = c & 0xFF;

               /* имеет атрибуты ? */
               c >>= 8; c &= 0xFF;
               *p++ = c ? c : ' ';
               if( c ) again = YES;
            }
            *s = '\0'; *p = '\0';
            printline(y, outbuf, attr, fpout);
         }
       npage++;        /* next page */
       resetsheet();   /* зачистить новый лист */
    }

    /* Напечатать одну строку листа */
    void printline ( int y, char *s, char *attr,
                    FILE *fpout ){
       register x;
       if( again ){
           doattr(attr, s); fprintf(fpout, "%s\r", attr );
       }
       fprintf(fpout, "%s", s);
       /* перевод листа или строки */
       fputc( y == LINES-1 ? FORMFEED : LINEFEED, fpout );
    }

    /* Проверить - нет ли атрибутов выделений */
    void doattr ( register char *abuf,
                  register char *vbuf ){
       for(; *abuf; abuf++, vbuf++ )
          if( !strchr(" _-!|\177", *abuf))
              *abuf = *vbuf;
    }
    /* Копирование файла на принтер */
    void printcopy ( FILE *fpin, FILE *fpout )
    {
       char inbuf[BUFSIZ];

       npage = 1; /* первая страница имеет номер 1 */
       fline = 0; /* текущая строка файла - 0      */

       resetsheet();  /* зачистить буфер листа */
       while( fgets(inbuf, sizeof inbuf - 1, fpin )
               != NULL ){
               register l = strlen( inbuf );
               if( l && inbuf[l-1] == '\n' )
                        inbuf[--l] =  '\0' ;
               fline++;
               untab   ( inbuf );
               addsheet( inbuf, fpout );
       }
       if( !(Sline == topline && Shalf == 0))
       /* если страница не была только что зачищена ... */
             flushsheet(fpout);
       fprintf(stderr, "%d строк, %d листов.\n",
                        fline,    npage-1);
    }
    /* Вызов: pr < файл > /dev/lp */
    void main (){ printcopy(stdin, stdout); }

Файл-принтер имеет в UNIX имя /dev/lp или подобное ему, а в MS DOS - имя prn.

7.32.

Напишите программу, которая построчно считывает небольшой файл в память и печатает строки в обратном порядке. Указание: используйте динамическую память функции malloc() и strcpy().

Объясним, почему желательно пользоваться динамической памятью. Пусть мы знаем, что строки имеют максимальную длину 80 символов и максимальное количество строк равно 50. Мы могли бы хранить текст в двумерном массиве:

    char text[50][80];

занимающем 50*80 = 4000 байт памяти. Пусть теперь оказалось, что строки файла в действительности имеют длину по 10 букв. Мы

    используем           50 * (10 + 1) =  550 байт
    не используем 4000 - 50 * (10 + 1) = 3450 байт

(+1 нужен для символа '\0' на конце строки).

Пусть мы теперь пишем

    char *text[50]; int i=0;

и при чтении очередной строки сохраняем ее так:

    char buffer[81], *malloc(),  *gets();
    while( gets(buffer) != NULL ){
      text[i] = (char *) malloc(strlen(buffer)+1);
      /* +1 для хранения \0, который не учтен strlen-ом */
      strcpy(text[i++], buffer);
    }

то есть заказываем ровно столько памяти, сколько надо для хранения строки и ни байтом больше. Здесь мы (если sizeof(char *)==4) используем

               50 * 4 +  50 * (10 + 1 + 4) = 950 байт
    массив указателей + заказанная malloc память

(+4 - служебная информация malloc), но зато у нас не остается неиспользуемой памяти. Преимуществом выделения памяти в виде массива является то, что эта память выделится ГАРАНТИРОВАННО, тогда как malloc()-у может не хватить памяти (если мы ее прежде очень много захватывали и не освобождали free()). Если malloc не может выделить участок памяти требуемого размера, он возвращает значение NULL:

    if((text[i] = malloc(....)) == NULL)
    { fprintf(stderr, "Мало памяти\n"); break; }

Распечатка строк:

    for(--i; i >= 0; i-- ){
            printf("%s\n", text[i]);
            free( text[i] );
    }

Функция free(ptr) "освобождает"* отведенную ранее malloc()ом или calloc()ом область памяти по адресу ptr так, что при новых вызовах malloc() эта область может быть переиспользована. Данные в освобожденной памяти ПОРТЯТСЯ после free(). Ошибочно (и опасно) освобождать память, которая НЕ БЫЛА отведена malloc()-ом!

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

7.33.

Напишите программу, печатающую строки файла в обратном порядке. Не считывать файл целиком в память! Следует использовать метод "обратного чтения" либо метод "быстрого доступа" к строкам файла, описанный в главе "Работа с файлами".

    /* Инвертирование порядка строк в файле.
     * Используется та идея, что файл-результат имеет тот же
     * размер, что и исходный
     */
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>

    #define BUFS 4096       /* максимальная длина строки */

    void main(int argc, char **argv )
    {
            FILE *fp;
            struct stat st;
            long len;
            char buffer[ BUFS+1 ];
            FILE *fpnew;      /* инверсный файл */
            int lgt;

            if( argc != 2 ){
                    printf("Error: must be filename\n");
                    exit(1);
            }
            if( (fp= fopen( argv[1], "r" )) == NULL ){
                    printf( "Can not open %s\n", argv[1] );
                    exit(2);
            }
            stat( argv[1], &st );   /* fstat(fileno(fp), &st); */
            len = st.st_size;       /* длина файла в байтах */

            if( (fpnew = fopen( "inv.out", "w" ))== NULL ){
                    printf("Can not create file\n");
                    exit(3);
            }

            while( fgets( buffer, sizeof buffer, fp ) != NULL ){
                    lgt = strlen( buffer );
                    fseek(fpnew, len - lgt , 0);
                    /* Помните, что смещение у lseek и fseek
                     * это число типа long, а не int.
                     * Поэтому лучше всегда писать
                     *      lseek(fd, (long) off, whence);
                     */
                    len -= lgt;
                    fprintf( fpnew, "%s", buffer );
                    /* или лучше  fputs(buffer, fpnew); */
            }
            fclose( fp ); fclose( fpnew );
    }

7.34.

Напишите программу, которая читает файл, состоящий из "блоков" текста, разделенных пустыми строками. Размер "блока" ограничен. Программа готовит файл для печати на принтер так, чтобы ни один блок не разбивался на части:

        -----------             ----------   |###### A |             |###### A | лист1
        |####   A | превращать  |####   A |
        |#####  A |     в       |#####  A |
        |         |             |         |
        |###### B |             |         |
        -----------             ----------   |####   B |             |###### B | лист2
        |         |             |####   B |
           ...                  |         |

то есть если блок не умещается на остатке листа, он должен быть перенесен на следующий лист. Блоки следует разделять одной пустой строкой (но первая строка листа не должна быть пустой!). Если блок длиннее страницы - не переносите его.

    /* Решение задачи о переносе блоков текста,
     * если они не умещаются на остатке листа */

    #include <stdio.h>
    #include <ctype.h>
    extern  void *malloc(unsigned);
    extern  int atoi(char *);
    FILE    *fpin = stdin, *fpout = stdout;

    /* Спасти строку в динамически выделенной памяти */
    char *strdup (const char *s) {
        char   *ptr = (char *) malloc (strlen (s) + 1);
        if( ptr ) strcpy (ptr, s); return ptr;
    }
    int     page_length = 66;  /* длина страницы */
    int     current_line;      /* текущая строка на странице (с нуля) */
    int numbered = 0;          /* нумеровать строки листа ? */
    #define MAXLINES 256       /* макс. длина блока  */
    int     stored = 0;        /* запомнено строк    */
    char   *lines[MAXLINES];   /* запомненные строки */

    /* Запомнить строку блока в буфер строк */
    void remember (char *s) {
        if (stored >= MAXLINES) {
            fprintf (stderr, "Слишком длинный блок.\n"); return;
        } else if((lines[stored++] = strdup (s)) == NULL ){
            fprintf (stderr, "Мало памяти (Out of memory).\n"); exit(13);
        }
    }

    /* Переход на следующую страницу */
    void newpage () {
        current_line = 0; putc('\f', fpout);
    }

    /* Перевод строки или листа */
    void newline (void) {
        if (current_line == page_length - 1)
            newpage ();             /* начать новый лист */
        else {
            current_line++;
            if( numbered ) fprintf(fpout, "%02d\n", current_line);
            else           putc ('\n', fpout);
        }
    }

    /* Переход на следующую страницу вставкой пустых строк */
    void nextpage () {
        while (current_line != 0)
               newline ();
    }

    /* Выдать спасенный блок */
    void throwout () {
        register    i;
        for (i = 0; i < stored; i++) {
            if( numbered )
                 fprintf(fpout, "%02d %s", current_line, lines[i]);
            else fputs (lines[i], fpout);
            newline (); free (lines[i]);
        }
        stored = 0;
    }

    /* Выдать блок, перенося на следующий лист если надо */
    void flush () {
        int     rest_of_page = page_length - current_line;
        /* осталось пустых строк на странице */

        if ((stored > page_length && rest_of_page < page_length / 4) ||
             rest_of_page < stored)
                 nextpage ();
        throwout ();
        if (current_line)           /* не первая строка листа */
            newline ();             /* разделитель блоков */
    }

    /* Обработать входной файл */
    void process () {
        char buffer[512];  int l;

        while (fgets (buffer, sizeof buffer, fpin) != NULL) {
            if ((l = strlen (buffer)) && buffer[l - 1] == '\n')
                                         buffer[  --l] =  '\0';
            if (l) remember (buffer);
        /* а по пустой строке - выдать блок */
            else if (stored) flush ();
        }
        if (stored) flush ();
        nextpage();
    }

    void main (int argc, char *argv[]) {
        argc--; argv++;
        while (*argv) {
            if (**argv == '-') {
                char   *key = *argv + 1, *arg;
                switch (*key) {
                    case 'l':
                        if (! key[1]) {
                            if( argv[1] ){
                              arg = argv[1]; argv++; argc--;
                            } else arg = "";
                        } else arg = key+1;
                        if( isdigit(*arg) ){
                           page_length = atoi(arg);
           fprintf (stderr, "Длина страницы: %d строк\n", page_length);
                        } else fprintf(stderr, "-l ЧИСЛО\n");
                        break;
                    case 'n':
                        numbered++; break;
                    default:
                        fprintf (stderr, "Неизвестный ключ %s\n", key);
                        break;
                }
            }
            argv++; argc--;
        }
        process ();
        exit(0);
    }

* - На самом деле все освобожденные куски включаются в список свободной памяти, и склеиваются вместе, если два освобожденных куска оказались рядом. При новых вызовах malloc сначала просматривается список свободной памяти - нет ли там области достаточного размера? Этот алгоритм описан у Кернигана и Ритчи.

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед