Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
80
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 1992-96

- 251 -

Си в UNIX™

строка печатается повторно, при этом символы, которые не должны печататься жирно, следует заменить на пробелы (или на табуляцию, если этот символ сам есть '\t').

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 );

А. Богатырёв, 1992-96

- 252 -

Си в UNIX™

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;

А. Богатырёв, 1992-96

- 253 -

Си в UNIX™

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();

/*

зачистить новый лист */

А. Богатырёв, 1992-96 - 254 - Си в UNIX™

}

/* Напечатать одну строку листа */

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' на конце строки). Пусть мы теперь пишем

А. Богатырёв, 1992-96

- 255 -

Си в UNIX™

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

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

А. Богатырёв, 1992-96

- 256 -

Си в UNIX™

/* Инвертирование порядка строк в файле.

*Используется та идея, что файл-результат имеет тот же

*размер, что и исходный

*/

#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 |

...

 

 

|

|

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

А. Богатырёв, 1992-96

- 257 -

Си в UNIX™

длиннее страницы - не переносите его.

 

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

 

* если они не умещаются на остатке листа */

 

#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;

}

А. Богатырёв, 1992-96

- 258 -

Си в UNIX™

/* Выдать блок, перенося на следующий лист если надо */ 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);

}

7.35. Составьте программу вывода строк файла в инверсном отображении, причем порядок символов в строках также следует инвертировать. Например,

abcdef ... oklmn

 

987654321

.....

превращать в

.....

123456789

 

nmlko ... fedcba

Программа должна быть составлена двумя способами: при помощи обратного чтения файла и рекурсивным вызовом самой функции инвертирования. Указание: при обратном чтении надо читать файл большими

А. Богатырёв, 1992-96

- 259 -

Си в UNIX™

кусками (блоками).

7.36. Напишите программу, читающую файл построчно и размещающую строки в отсортированное двоичное дерево. По концу файла - распечатайте это дерево. Указание: используйте динамическую память и рекурсию.

/* Двоичная сортировка строк при помощи дерева */ #include <stdio.h>

char buf[240];

 

/* буфер ввода */

 

int lines;

 

/* номер строки файла */

typedef struct

node{

 

 

struct

_data{

/* ДАННЫЕ */

 

char *key;

/* ключ - строка

*/

int

line;

/* номер строки

*/

} data;

 

 

/* СЛУЖЕБНАЯ ИНФОРМАЦИЯ */ struct node *l, /* левое поддерево */

 

*r; /* правое

поддерево */

} Node;

 

 

Node *root = NULL;

/* корень

дерева (ссылка на верхний узел) */

/* Отведение памяти и инициализация нового узла */

Node *newNode(s)

 

 

char *s;

/* строка

*/

{

 

 

Node *tmp;

 

 

extern char *malloc();

/* выделитель памяти */

tmp = (Node *) malloc(sizeof(Node)); if( tmp == NULL ){

fprintf( stderr, "Нет памяти.\n");

exit(1);

 

}

 

tmp -> l = tmp -> r = NULL;

/* нет поддеревьев */

tmp -> data.line = lines;

/* номер строки файла */

tmp -> data.key =

malloc( strlen(s)

+ 1 );

/* +1

-

под

байт '\0' в конце

строки */

strcpy(tmp

->

data.key, s);

/* копируем ключ в узел */

return tmp;

}

int i; /* Вынесено в статическую память, чтобы при каждом

*рекурсивном вызове не создавалась новая auto-переменная,

*а использовалась одна и та же статическая */

А. Богатырёв, 1992-96

- 260 -

Си в UNIX™

/* Рекурсивная печать дерева */ void printtree(root, tree, level, c)

Node *root;

/* корень дерева */

Node *tree;

/* дерево

*/

int level;

/* уровень

*/

char c;

/* имя поддерева */

{

if( root == NULL ){ printf("Дерево пусто.\n"); return; } if( tree == NULL ) return;

/* если есть - распечатать левое поддерево */ printtree (root, tree -> l, level + 1, '/'); /* 'L' */

/* распечатать ключ узла */ for( i=0; i < level; i++ )

printf(" "); printf("%c%3d--\"%s\"\n",

c, tree-> data.line, tree -> data.key);

/* если есть - распечатать правое поддерево */ printtree(root, tree -> r, level + 1, '\\'); /* 'R' */

}

void prTree(tree) Node *tree;

{

printtree(tree, tree, 0, '*');

}

 

 

 

 

/* Добавить узел с ключом key в дерево tree

*/

 

void addnode(tree, key)

 

 

 

Node **tree;

/* в какое дерево добавлять: адрес переменной,

 

* содержащей ссылку на корневой узел */

char *key;

/* ключ узла */

 

 

 

{

 

 

 

 

#define TREE (*tree)

 

 

 

if( TREE == NULL ){ /* дерево пока пусто */

 

TREE = newNode( key );

 

 

 

 

return;

 

 

 

}

 

 

 

 

/* иначе есть хоть один узел

*/

 

 

if ( strcmp (key, TREE -> data.key) < 0 )

 

{

 

 

 

 

 

/* добавить в левое поддерево

*/

 

if ( TREE -> l == NULL ){

 

 

 

/* нет левого дерева

*/

 

 

TREE -> l = newNode(key);

 

 

return;

 

 

 

}

else addnode( & TREE ->l , key);

}

else{

/* добавить в правое дерево */ if ( TREE -> r == NULL ){

/* нет правого поддерева */ TREE -> r = newNode(key); return;

}

else addnode ( & TREE ->r, key) ;

}

}