Страница:
nline++; /* номер строки */
}/* endwhile */
if( nline ) puts(prev);
/* последняя строка всегда выдается */
}
7.13. Составьте программу, которая будет удалять в конце (и в начале) каждой строки
файла пробелы и табуляции, а также удалять строки, целиком состоящие из пробелов и
табуляций.
7.14. Для экономии места в файле, редакторы текстов при записи отредактированного
файла сжимают подряд идущие пробелы в табуляцию. Часто это неудобно для программ
обработки текстов (поскольку требует особой обработки табуляций - это ОДИН символ,
который на экране и в тексте занимает НЕСКОЛЬКО позиций!), поэтому при чтении файла
мы должны расширять табуляции в нужное количество пробелов, например так:
/* заменять табуляции на пробелы */
void untab(s) register char *s;
{
char newstr[256]; /* новая строка */
char *src = s;
int n; /* счетчик */
register dstx; /* координата x в новой строке */
for(dstx = 0; *s != '\0'; s++)
if( *s == '\t'){
for(n = 8 - dstx % 8 ; n > 0 ; n--)
newstr[dstx++] = ' ';
}else newstr[dstx++] = *s;
newstr[dstx] = '\0';
strcpy(src, newstr); /* строку на старое место */
}
7.15. Напишите обратную функцию, сжимающую подряд идущие пробелы в табуляции.
А. Богатырев, 1992-95 - 286 - Си в UNIX
void tabify(){
int chr;
int icol, ocol; /* input/output columns */
for(icol = ocol = 0; ; ){
if((chr = getchar()) == EOF)
break;
switch(chr){
case ' ':
icol++;
break;
case '\n':
case '\r':
ocol = icol = 0;
putchar(chr);
break;
case '\t':
icol += 8;
icol &= ~07; /* icol -= icol % 8; */
break;
default:
while(((ocol + 8) & ~07) <= icol){
#ifdef NOTDEF
if(ocol + 1 == icol)
break;
/* взять ' ' вместо '\t' */
#endif
putchar('\t');
ocol += 8;
ocol &= ~07;
}
while(ocol < icol){
putchar(' ');
ocol++;
}
putchar(chr);
icol++;
ocol++;
break;
}
}
}
7.16. Составьте программу, укорачивающую строки исходного файла до заданной величины
и помещающую результат в указанный файл. Учтите, что табуляция разворачивается в нес-
колько пробелов!
7.17. Разработайте программу, укорачивающую строки входного файла до 60 символов.
Однако теперь запрещается обрубать слова.
А. Богатырев, 1992-95 - 287 - Си в UNIX
7.18. Разработайте программу, заполняющую промежутки между словами строки дополни-
тельными пробелами таким образом, чтобы длина строки была равна 60 символам.
7.19. Напишите программу, переносящую слишком длинные строки. Слова разбивать
нельзя (неумешающееся слово следует перенести целиком). Ширину строки считать равной
60.
7.20. Составьте программу, центрирующую строки файла относительно середины экрана,
т.е. добавляющую в начало строки такое количество пробелов, чтобы середина строки
печаталась в 40-ой позиции (считаем, что обычный экран имеет ширину 80 символов).
7.21. Напишите программу, отсекающую n пробелов в начале каждой строки (или n первых
любых символов). Учтите, что в файле могут быть строки короче n (например пустые
строки).
#include <stdio.h>
/* ... текст функции untab(); ... */
void process(char name[], int n, int spacesOnly){
char line[256]; int length, shift, nline = 0;
char newname[128]; FILE *fpin, *fpout;
if((fpin = fopen(name, "r")) == NULL){
fprintf(stderr, "Не могу читать %s\n", name);
return;
}
sprintf(newname, "_%s", name); /* например */
if((fpout = fopen(newname, "w")) == NULL){
fprintf(stderr, "Не могу создать %s\n",
newname); fclose(fpin); return;
}
while(fgets(line, sizeof line, fpin)){ ++nline;
if((length = strlen(line)) &&
line[length-1] == '\n')
line[--length] = '\0'; /* обрубить '\n' */
untab(line); /* развернуть табуляции */
for(shift=0; line[shift] != '\0' && shift < n ;
++shift)
if(spacesOnly && line[shift] != ' ') break;
if(*line && shift != n ) /* Предупреждение */
fprintf(stderr,
"Начало строки #%d слишком коротко\n", nline);
fprintf(fpout, "%s\n", line+shift);
/* нельзя было fputs(line+n, fpout);
* т.к. эта позиция может быть ЗА концом строки
*/
}
fclose(fpin); fclose(fpout);
}
void main(int argc, char **argv){
if( argc != 3 ) exit(1);
process(argv[2], atoi(argv[1]) /* 8 */, 1);
exit(0);
}
7.22. Напишите программу, разбивающую файл на два по вертикали: в первый файл попа-
дает левая половина исходного файла, во второй - правая. Ширину колонки задавайте из
аргументов main(). Если же аргумент не указан - 40 позиций.
7.23. Напишите программу сортировки строк в алфавитном порядке. Учтите, что функция
strcmp() сравнивает строки в порядке кодировки, принятой на данной конкретной машине.
Русские буквы, как правило, идут не в алфавитном порядке! Следует написать функцию
А. Богатырев, 1992-95 - 288 - Си в UNIX
для алфавитного сравнения отдельных символов и, пользуясь ею, переписать функцию
strcmp().
7.24. Отсортируйте массив строк по лексикографическому убыванию, игнорируя различия
между строчными и прописными буквами.
7.25. Составьте программу дихотомического поиска в отсортированном массиве строк
(методом деления пополам).
/* Поиск в таблице методом половинного деления: dihotomia */
#include <stdio.h>
struct elem {
char *name; /* ключ поиска */
int value;
} table[] = {
/* имена строго по алфавиту */
{ "andrew", 17 },
{ "bill", 23 },
{ "george", 55 },
{ "jack", 54 },
{ "jaw", 43 },
{ "john", 33 },
{ "mike", 99 },
{ "paul", 21 },
{ "sue", 66 }, /* SIZE - 2 */
{ NULL, -1 }, /* SIZE - 1 */
/* NULL введен только для распечатки таблицы */
};
#define SIZE (sizeof(table) / sizeof(struct elem))
/* Дихотомический поиск по таблице */
struct elem *find(s, table, size)
char *s; /* что найти ? */
struct elem table[]; /* в чем ? */
int size; /* среди первых size элементов */
{
register top, bottom, middle;
register code;
top = 0; /* начало */
bottom = size - 1; /* конец: индекс строки "sue" */
while( top <= bottom ){
middle = (top + bottom) / 2; /* середина */
/* сравнить строки */
code = strcmp( s, table[middle].name ) ;
if( code > 0 ){
top = middle + 1;
}else if( code < 0 ){
bottom = middle - 1;
}else return &table[ middle ];
}
return (struct elem *) NULL; /* не нашел */
}
А. Богатырев, 1992-95 - 289 - Си в UNIX
/* распечатка таблицы */
void printtable(tbl) register struct elem *tbl; {
for( ; tbl->name != NULL ; tbl++ ){
printf( "%-15s %d\n", tbl->name, tbl->value );
}
}
int main(){
char buf[80];
struct elem *ptr;
printtable(table);
for(;;){
printf( "-> " );
if( gets( buf ) == NULL) break; /* EOF */
if( ! strcmp( buf, "q" ))
exit(0); /* quit: выход */
ptr = find( buf, table, SIZE-1 );
if( ptr )
printf( "%d\n", ptr->value );
else {
printf( "--- Не найдено ---\n" );
printtable(table);
}
}
return 0;
}
7.26. Напишем функцию, которая преобразует строку так, что при ее печати буквы в ней
будут подчеркнуты, а цифры - выделены жирно. Формат текста с выделениями, который
создается этим примером, является общепринятым в UNIX и распознается некоторыми прог-
раммами: например, программа просмотра файлов less (more) выделяет такие буквы на
экране специальными шрифтами или инверсией фона.
#define LEN 9 /* потом напишите 256 */
char input[] = "(xxx+yyy)/123.75=?";
char output[LEN];
void main( void ){
int len=LEN, i; void bi_conv(); char c;
bi_conv(input, output, &len);
if(len > LEN){
printf("Увеличь LEN до %d\n", len);
len = LEN; /* доступный максимум */
}
for(i=0; i < len && (c = output[i]); ++i)
putchar(c);
putchar('\n');
}
/* Заметьте, что include-файлы не обязательно
* должны включаться в самом начале программы! */
#include <stdio.h>
#include <ctype.h>
#define PUT(c) { count++; \
if(put < *len){ *p++ = (c); ++put;}}
#define GET() (*s ? *s++ : EOF)
void bi_conv(
А. Богатырев, 1992-95 - 290 - Си в UNIX
/*IN*/ char *s,
/*OUT*/ char *p,
/*INOUT*/ int *len ){
int count, put, c;
for(count=put=0; (c=GET()) != EOF; ){
/* жирный: C\bC */
/* подчеркнутый: _\bC */
if(isalpha(c)){ PUT('_'); PUT('\b'); }
else if(isdigit(c)){ PUT( c ); PUT('\b'); }
PUT(c);
}
PUT('\0'); /* закрыть строку */
*len = count;
#undef PUT
#undef GET
}
Напишите программу для подобной обработки файла. Заметим, что для этого не нужны
промежуточные строки input и output и построчное чтение файла; все, что надо сделать,
это определить
#define PUT(c) if(c)putchar(c)
#define GET() getchar()
Напишите подобную функцию, удваивающую буквы в ссттррооккее.
7.27. Напишите программу, удаляющую из файла выделения. Для этого надо просто уда-
лять последовательности вида C\b
#include <stdio.h>
#define NOPUT (-1) /* не символ ASCII */
/* Названия шрифтов - в перечислимом типе */
typedef enum { NORMAL=1, ITALICS, BOLD, RED=BOLD } font;
int ontty; font textfont; /* текущее выделение */
#define setfont(f) textfont=(f)
#define getfont() (textfont)
#define SetTtyFont(f) if(ontty) tfont(f)
/* Установить выделение на экране терминала */
void tfont(font f){ /* только для ANSI терминала */
static font ttyfont = NORMAL;
if(ttyfont == f) return;
printf("\033[0m"); /* set NORMAL font */
switch(ttyfont = f){
case NORMAL: /* уже сделано выше */ break;
case BOLD: printf("\033[1m"); break;
case ITALICS: /* use reverse video */
printf("\033[7m"); break;
}
}
void put(int c){ /* Вывод символа текущим цветом */
if(c == NOPUT) return; /* '\b' */
SetTtyFont(getfont()); putchar(c);
setfont(NORMAL); /* Ожидать новой C\b посл-ти */
}
void
main(){ register int c, cprev = NOPUT;
/* Стандартный вывод - это терминал ? */
ontty = isatty(fileno(stdout));
setfont(NORMAL);
while((c = getchar()) != EOF){
А. Богатырев, 1992-95 - 291 - Си в UNIX
if(c == '\b'){ /* выделение */
if((c = getchar()) == EOF) break;
if(c == cprev) setfont(BOLD);
else if(cprev == '_') setfont(ITALICS);
else /* наложение A\bB */ setfont(RED);
} else put(cprev);
cprev = c;
}
put(cprev); /* последняя буква файла */
SetTtyFont(NORMAL);
}
7.28. Напишите программу печати на принтере листинга Си-программ. Ключевые слова
языка выделяйте двойной надпечаткой. Для выдачи на терминал напишите программу, под-
черкивающую ключевые слова (подчеркивание - в следующей строке). Упрощение: выде-
ляйте не ключевые слова, а большие буквы. Указание: для двойной печати используйте
управляющий символ '\r' - возврат к началу той же строки; затем строка печатается
повторно, при этом символы, которые не должны печататься жирно, следует заменить на
пробелы (или на табуляцию, если этот символ сам есть '\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");
А. Богатырев, 1992-95 - 292 - Си в UNIX
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 ){ /* выделить память */
А. Богатырев, 1992-95 - 293 - Си в UNIX
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;
А. Богатырев, 1992-95 - 294 - Си в UNIX
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;
}
А. Богатырев, 1992-95 - 295 - Си в UNIX
/* Копирование файла на принтер */
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) используем
А. Богатырев, 1992-95 - 296 - Си в UNIX
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-95 - 297 - Си в 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. Напишите программу, которая читает файл, состоящий из "блоков" текста, разде-
ленных пустыми строками. Размер "блока" ограничен. Программа готовит файл для печати
на принтер так, чтобы ни один блок не разбивался на части:
А. Богатырев, 1992-95 - 298 - Си в UNIX
----------- -----------
|###### 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);
}
А. Богатырев, 1992-95 - 299 - Си в UNIX
/* Перевод строки или листа */
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();
}
А. Богатырев, 1992-95 - 300 - Си в UNIX
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
Программа должна быть составлена двумя способами: при помощи обратного чтения файла и
рекурсивным вызовом самой функции инвертирования. Указание: при обратном чтении надо
}/* endwhile */
if( nline ) puts(prev);
/* последняя строка всегда выдается */
}
7.13. Составьте программу, которая будет удалять в конце (и в начале) каждой строки
файла пробелы и табуляции, а также удалять строки, целиком состоящие из пробелов и
табуляций.
7.14. Для экономии места в файле, редакторы текстов при записи отредактированного
файла сжимают подряд идущие пробелы в табуляцию. Часто это неудобно для программ
обработки текстов (поскольку требует особой обработки табуляций - это ОДИН символ,
который на экране и в тексте занимает НЕСКОЛЬКО позиций!), поэтому при чтении файла
мы должны расширять табуляции в нужное количество пробелов, например так:
/* заменять табуляции на пробелы */
void untab(s) register char *s;
{
char newstr[256]; /* новая строка */
char *src = s;
int n; /* счетчик */
register dstx; /* координата x в новой строке */
for(dstx = 0; *s != '\0'; s++)
if( *s == '\t'){
for(n = 8 - dstx % 8 ; n > 0 ; n--)
newstr[dstx++] = ' ';
}else newstr[dstx++] = *s;
newstr[dstx] = '\0';
strcpy(src, newstr); /* строку на старое место */
}
7.15. Напишите обратную функцию, сжимающую подряд идущие пробелы в табуляции.
А. Богатырев, 1992-95 - 286 - Си в UNIX
void tabify(){
int chr;
int icol, ocol; /* input/output columns */
for(icol = ocol = 0; ; ){
if((chr = getchar()) == EOF)
break;
switch(chr){
case ' ':
icol++;
break;
case '\n':
case '\r':
ocol = icol = 0;
putchar(chr);
break;
case '\t':
icol += 8;
icol &= ~07; /* icol -= icol % 8; */
break;
default:
while(((ocol + 8) & ~07) <= icol){
#ifdef NOTDEF
if(ocol + 1 == icol)
break;
/* взять ' ' вместо '\t' */
#endif
putchar('\t');
ocol += 8;
ocol &= ~07;
}
while(ocol < icol){
putchar(' ');
ocol++;
}
putchar(chr);
icol++;
ocol++;
break;
}
}
}
7.16. Составьте программу, укорачивающую строки исходного файла до заданной величины
и помещающую результат в указанный файл. Учтите, что табуляция разворачивается в нес-
колько пробелов!
7.17. Разработайте программу, укорачивающую строки входного файла до 60 символов.
Однако теперь запрещается обрубать слова.
А. Богатырев, 1992-95 - 287 - Си в UNIX
7.18. Разработайте программу, заполняющую промежутки между словами строки дополни-
тельными пробелами таким образом, чтобы длина строки была равна 60 символам.
7.19. Напишите программу, переносящую слишком длинные строки. Слова разбивать
нельзя (неумешающееся слово следует перенести целиком). Ширину строки считать равной
60.
7.20. Составьте программу, центрирующую строки файла относительно середины экрана,
т.е. добавляющую в начало строки такое количество пробелов, чтобы середина строки
печаталась в 40-ой позиции (считаем, что обычный экран имеет ширину 80 символов).
7.21. Напишите программу, отсекающую n пробелов в начале каждой строки (или n первых
любых символов). Учтите, что в файле могут быть строки короче n (например пустые
строки).
#include <stdio.h>
/* ... текст функции untab(); ... */
void process(char name[], int n, int spacesOnly){
char line[256]; int length, shift, nline = 0;
char newname[128]; FILE *fpin, *fpout;
if((fpin = fopen(name, "r")) == NULL){
fprintf(stderr, "Не могу читать %s\n", name);
return;
}
sprintf(newname, "_%s", name); /* например */
if((fpout = fopen(newname, "w")) == NULL){
fprintf(stderr, "Не могу создать %s\n",
newname); fclose(fpin); return;
}
while(fgets(line, sizeof line, fpin)){ ++nline;
if((length = strlen(line)) &&
line[length-1] == '\n')
line[--length] = '\0'; /* обрубить '\n' */
untab(line); /* развернуть табуляции */
for(shift=0; line[shift] != '\0' && shift < n ;
++shift)
if(spacesOnly && line[shift] != ' ') break;
if(*line && shift != n ) /* Предупреждение */
fprintf(stderr,
"Начало строки #%d слишком коротко\n", nline);
fprintf(fpout, "%s\n", line+shift);
/* нельзя было fputs(line+n, fpout);
* т.к. эта позиция может быть ЗА концом строки
*/
}
fclose(fpin); fclose(fpout);
}
void main(int argc, char **argv){
if( argc != 3 ) exit(1);
process(argv[2], atoi(argv[1]) /* 8 */, 1);
exit(0);
}
7.22. Напишите программу, разбивающую файл на два по вертикали: в первый файл попа-
дает левая половина исходного файла, во второй - правая. Ширину колонки задавайте из
аргументов main(). Если же аргумент не указан - 40 позиций.
7.23. Напишите программу сортировки строк в алфавитном порядке. Учтите, что функция
strcmp() сравнивает строки в порядке кодировки, принятой на данной конкретной машине.
Русские буквы, как правило, идут не в алфавитном порядке! Следует написать функцию
А. Богатырев, 1992-95 - 288 - Си в UNIX
для алфавитного сравнения отдельных символов и, пользуясь ею, переписать функцию
strcmp().
7.24. Отсортируйте массив строк по лексикографическому убыванию, игнорируя различия
между строчными и прописными буквами.
7.25. Составьте программу дихотомического поиска в отсортированном массиве строк
(методом деления пополам).
/* Поиск в таблице методом половинного деления: dihotomia */
#include <stdio.h>
struct elem {
char *name; /* ключ поиска */
int value;
} table[] = {
/* имена строго по алфавиту */
{ "andrew", 17 },
{ "bill", 23 },
{ "george", 55 },
{ "jack", 54 },
{ "jaw", 43 },
{ "john", 33 },
{ "mike", 99 },
{ "paul", 21 },
{ "sue", 66 }, /* SIZE - 2 */
{ NULL, -1 }, /* SIZE - 1 */
/* NULL введен только для распечатки таблицы */
};
#define SIZE (sizeof(table) / sizeof(struct elem))
/* Дихотомический поиск по таблице */
struct elem *find(s, table, size)
char *s; /* что найти ? */
struct elem table[]; /* в чем ? */
int size; /* среди первых size элементов */
{
register top, bottom, middle;
register code;
top = 0; /* начало */
bottom = size - 1; /* конец: индекс строки "sue" */
while( top <= bottom ){
middle = (top + bottom) / 2; /* середина */
/* сравнить строки */
code = strcmp( s, table[middle].name ) ;
if( code > 0 ){
top = middle + 1;
}else if( code < 0 ){
bottom = middle - 1;
}else return &table[ middle ];
}
return (struct elem *) NULL; /* не нашел */
}
А. Богатырев, 1992-95 - 289 - Си в UNIX
/* распечатка таблицы */
void printtable(tbl) register struct elem *tbl; {
for( ; tbl->name != NULL ; tbl++ ){
printf( "%-15s %d\n", tbl->name, tbl->value );
}
}
int main(){
char buf[80];
struct elem *ptr;
printtable(table);
for(;;){
printf( "-> " );
if( gets( buf ) == NULL) break; /* EOF */
if( ! strcmp( buf, "q" ))
exit(0); /* quit: выход */
ptr = find( buf, table, SIZE-1 );
if( ptr )
printf( "%d\n", ptr->value );
else {
printf( "--- Не найдено ---\n" );
printtable(table);
}
}
return 0;
}
7.26. Напишем функцию, которая преобразует строку так, что при ее печати буквы в ней
будут подчеркнуты, а цифры - выделены жирно. Формат текста с выделениями, который
создается этим примером, является общепринятым в UNIX и распознается некоторыми прог-
раммами: например, программа просмотра файлов less (more) выделяет такие буквы на
экране специальными шрифтами или инверсией фона.
#define LEN 9 /* потом напишите 256 */
char input[] = "(xxx+yyy)/123.75=?";
char output[LEN];
void main( void ){
int len=LEN, i; void bi_conv(); char c;
bi_conv(input, output, &len);
if(len > LEN){
printf("Увеличь LEN до %d\n", len);
len = LEN; /* доступный максимум */
}
for(i=0; i < len && (c = output[i]); ++i)
putchar(c);
putchar('\n');
}
/* Заметьте, что include-файлы не обязательно
* должны включаться в самом начале программы! */
#include <stdio.h>
#include <ctype.h>
#define PUT(c) { count++; \
if(put < *len){ *p++ = (c); ++put;}}
#define GET() (*s ? *s++ : EOF)
void bi_conv(
А. Богатырев, 1992-95 - 290 - Си в UNIX
/*IN*/ char *s,
/*OUT*/ char *p,
/*INOUT*/ int *len ){
int count, put, c;
for(count=put=0; (c=GET()) != EOF; ){
/* жирный: C\bC */
/* подчеркнутый: _\bC */
if(isalpha(c)){ PUT('_'); PUT('\b'); }
else if(isdigit(c)){ PUT( c ); PUT('\b'); }
PUT(c);
}
PUT('\0'); /* закрыть строку */
*len = count;
#undef PUT
#undef GET
}
Напишите программу для подобной обработки файла. Заметим, что для этого не нужны
промежуточные строки input и output и построчное чтение файла; все, что надо сделать,
это определить
#define PUT(c) if(c)putchar(c)
#define GET() getchar()
Напишите подобную функцию, удваивающую буквы в ссттррооккее.
7.27. Напишите программу, удаляющую из файла выделения. Для этого надо просто уда-
лять последовательности вида C\b
#include <stdio.h>
#define NOPUT (-1) /* не символ ASCII */
/* Названия шрифтов - в перечислимом типе */
typedef enum { NORMAL=1, ITALICS, BOLD, RED=BOLD } font;
int ontty; font textfont; /* текущее выделение */
#define setfont(f) textfont=(f)
#define getfont() (textfont)
#define SetTtyFont(f) if(ontty) tfont(f)
/* Установить выделение на экране терминала */
void tfont(font f){ /* только для ANSI терминала */
static font ttyfont = NORMAL;
if(ttyfont == f) return;
printf("\033[0m"); /* set NORMAL font */
switch(ttyfont = f){
case NORMAL: /* уже сделано выше */ break;
case BOLD: printf("\033[1m"); break;
case ITALICS: /* use reverse video */
printf("\033[7m"); break;
}
}
void put(int c){ /* Вывод символа текущим цветом */
if(c == NOPUT) return; /* '\b' */
SetTtyFont(getfont()); putchar(c);
setfont(NORMAL); /* Ожидать новой C\b посл-ти */
}
void
main(){ register int c, cprev = NOPUT;
/* Стандартный вывод - это терминал ? */
ontty = isatty(fileno(stdout));
setfont(NORMAL);
while((c = getchar()) != EOF){
А. Богатырев, 1992-95 - 291 - Си в UNIX
if(c == '\b'){ /* выделение */
if((c = getchar()) == EOF) break;
if(c == cprev) setfont(BOLD);
else if(cprev == '_') setfont(ITALICS);
else /* наложение A\bB */ setfont(RED);
} else put(cprev);
cprev = c;
}
put(cprev); /* последняя буква файла */
SetTtyFont(NORMAL);
}
7.28. Напишите программу печати на принтере листинга Си-программ. Ключевые слова
языка выделяйте двойной надпечаткой. Для выдачи на терминал напишите программу, под-
черкивающую ключевые слова (подчеркивание - в следующей строке). Упрощение: выде-
ляйте не ключевые слова, а большие буквы. Указание: для двойной печати используйте
управляющий символ '\r' - возврат к началу той же строки; затем строка печатается
повторно, при этом символы, которые не должны печататься жирно, следует заменить на
пробелы (или на табуляцию, если этот символ сам есть '\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");
А. Богатырев, 1992-95 - 292 - Си в UNIX
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 ){ /* выделить память */
А. Богатырев, 1992-95 - 293 - Си в UNIX
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;
А. Богатырев, 1992-95 - 294 - Си в UNIX
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;
}
А. Богатырев, 1992-95 - 295 - Си в UNIX
/* Копирование файла на принтер */
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) используем
А. Богатырев, 1992-95 - 296 - Си в UNIX
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-95 - 297 - Си в 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. Напишите программу, которая читает файл, состоящий из "блоков" текста, разде-
ленных пустыми строками. Размер "блока" ограничен. Программа готовит файл для печати
на принтер так, чтобы ни один блок не разбивался на части:
А. Богатырев, 1992-95 - 298 - Си в UNIX
----------- -----------
|###### 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);
}
А. Богатырев, 1992-95 - 299 - Си в UNIX
/* Перевод строки или листа */
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();
}
А. Богатырев, 1992-95 - 300 - Си в UNIX
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
Программа должна быть составлена двумя способами: при помощи обратного чтения файла и
рекурсивным вызовом самой функции инвертирования. Указание: при обратном чтении надо