Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 1992-96 |
- 271 - |
Си в UNIX™ |
void main(int ac, char *av[]){
register char *s, *p; register n; extern int nbra; extern char *braslist[], *braelist[], *loc1, *loc2;
if( ac > 1 && !strcmp(av[1], "-a")){ ac--; av++; all++; } if(ac != 3){
fprintf(stderr, "Usage: %s [-a] pattern subst\n", av[0]); exit(1);
}
compile(av[1], compiled, compiled + sizeof compiled, EOL);
while( gets(line) != NULL ){
if( !step(s = line, compiled)){ printf("%s\n", line); continue;
}
do{
/* Печатаем начало строки */
for( ; s != loc1; s++) putchar(*s);
/* Делаем замену */ for(s=av[2]; *s; s++) if(*s == '\\'){
if(isdigit(s[1])){ /* сегмент */ int num = *++s - '1';
if(num < 0 || num >= nbra){
fprintf(stderr, "Bad block number %d\n", num+1); exit(2);
}
for(p=braslist[num]; p != braelist[num]; ++p) putchar(*p);
} else if(s[1] == '&'){
++s; /* вся сопоставленная строка */ for(p=loc1; p != loc2; ++p)
putchar(*p);
} else putchar(*++s);
}else putchar(*s);
}while(all && step(s = loc2, compiled));
/* Остаток |
строки */ |
|
for(s=loc2; *s; s++) putchar(*s); |
|
|
putchar('\n'); |
|
|
} /* endwhile */ |
|
|
} |
|
|
void regerr(int code){ char *msg; |
|
|
switch(code){ |
|
|
case 11: msg = "Range endpoint too large."; |
break; |
|
case 16: msg = "Bad number."; |
break; |
|
case 25: msg = "\\digit out of range."; |
break; |
|
case 36: msg = "Illegal or missing delimiter."; break; |
||
case 41: msg = "No remembered search string."; |
break; |
|
case 42: msg = "\\(~\\) imbalance."; |
break; |
|
case 43: msg = "Too many \\(."; |
break; |
case 44: msg = "More than 2 numbers given in \\{~\\\"}."; break; case 45: msg = "} expected after \\."; break;
case 46: msg = "First number exceeds second in \\{~\\}."; break;
case 49: msg = "[ ] imbalance."; |
break; |
case 50: msg = "Regular expression overflow."; |
break; |
default: msg = "Unknown error"; |
break; |
} fputs(msg, stderr); fputc('\n', stderr); exit(code);
}
А. Богатырёв, 1992-96 |
- 272 - |
Си в UNIX™ |
void prfields(){ |
|
|
int i; |
|
|
for(i=0; i < nbra; i++) |
|
|
prfield(i); |
|
|
} |
|
|
void prfield(int n){ |
|
|
char *fbeg = braslist[n], *fend = braelist[n]; |
|
|
printf("\\%d='", |
n+1); |
|
for(; fbeg != fend; fbeg++) putchar(*fbeg);
printf("'\n");
}
7.44. Составьте функцию поиска подстроки в строке. Используя ее, напишите программу поиска подстроки в текстовом файле. Программа должна выводить строки (либо номера строк) файла, в которых встретилась данная подстрока. Подстрока задается в качестве аргумента функции main().
/* Алгоритм быстрого поиска подстроки.
*Дж. Мур, Р. Бойер, 1976 Texas
*Смотри: Communications of the ACM 20, 10 (Oct., 1977), 762-772
*Этот алгоритм выгоден при многократном поиске образца в
*большом количестве строк, причем если они равной длины -
*можно сэкономить еще и на операции strlen(str).
*Алгоритм характерен тем, что при неудаче производит сдвиг не на
*один, а сразу на несколько символов вправо.
*В лучшем случае алгоритм делает slen/plen сравнений.
*/ |
|
char *pattern; |
/* образец (что искать) */ |
static int plen; |
/* длина образца */ |
static int d[256]; |
/* таблица сдвигов; в алфавите ASCII - |
|
* 256 букв. */ |
/* расстояние от конца образца до позиции i в нем */ |
|
#define DISTANCE(i) |
((plen-1) - (i)) |
А. Богатырёв, 1992-96 |
- 274 - |
Си в UNIX™ |
/* Печать найденных строк */ |
|
|
void pr(s, p, n, nl) char *s, *p; |
|
|
{ |
|
|
register i; |
|
|
printf("%4d\t%s\n", nl, s ); |
|
|
printf(" |
\t"); |
|
for(i = 0; i < n; i++ ) |
|
|
putchar( s[i] == '\t' ? '\t' : ' ' ); |
|
|
printf( "%s\n", p ); |
|
|
} |
|
|
/* Аналог программы fgrep */ |
|
|
#include <stdio.h> |
|
|
char str[ 1024 ]; |
/* буфер для прочитанной строки */ |
|
void main(ac, av) char **av;
{
int nline = 0; /* номер строки файла */ int ind;
int retcode = 1;
if(ac != 2){
fprintf(stderr, "Usage: %s 'pattern'\n", av[0] ); exit(33);
}
compilePatternBM( av[1] ); while( gets(str) != NULL ){
nline++;
if((ind = indexBM(str)) >= 0 ){ retcode = 0; /* O'KAY */ pr(str, pattern, ind, nline);
}
}
exit(retcode);
}
/* Пример работы алгоритма:
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
peter piper picked a peck of pickled peppers. peck
*/
7.45.Напишите аналогичную программу, выдающую все строки, удовлетворяющие упрощенному регулярному выражению, задаваемому как аргумент для main(). Используйте функцию match, написанную нами ранее. Вы написали аналог программы grep из UNIX (но с другим типом регулярного выражения, нежели в оригинале).
7.46.Составьте функцию expand(s1, s2), которая расширяет сокращенные обозначения вида a-z строки s1
в эквивалентный полный список abcd...xyz в строке s2. Допускаются сокращения для строчных и прописных букв и цифр. Учтите случаи типа a−b−c, a−z0−9 и −a−g (соглашение состоит в том, что символ "−", стоящий в начале или в конце, воспринимается буквально).
А. Богатырёв, 1992-96 |
- 275 - |
Си в UNIX™ |
7.47. Напишите программу, читающую файл и заменяющую строки вида
|<1 и более пробелов и табуляций><текст>
на пары строк
|.pp
|<текст>
(здесь | обозначает левый край файла, a <> - метасимволы). Это - простейший препроцессор, готовящий текст в формате nroff (это форматтер текстов в UNIX). Усложнения:
• строки, начинающиеся с точки или с апострофа, заменять на
\&<текст, начинающийся с точки или '>
• строки, начинающиеся с цифры, заменять на
.ip <число> <текст>
•символ \ заменять на последовательность \e.
•удалять пробелы перед символами .,;:!?) и вставлять после них пробел (знак препинания должен быть приклеен к концу слова, иначе он может быть перенесен на следующую строку. Вы когда-нибудь видели строку, начинающуюся с запятой?).
•склеивать перенесенные слова, поскольку nroff делает переносы сам:
....xxxx начало- |
=> ....xxxx началоконец |
конец yyyy...... |
yyyy................ |
Вызывайте этот препроцессор разметки текста так:
$prep файлы... | nroff -me > text.lp
7.48.Составьте программу преобразования прописных букв из файла ввода в строчные, используя при этом функцию, в которой необходимо организовать анализ символа (действительно ли это буква). Строчные буквы выдавать без изменения. Указание: используйте макросы из <ctype.h>.
Ответ:
#include <ctype.h> #include <stdio.h> main(){
int c;
while( (c = getchar()) != EOF ) putchar( isalpha( c ) ?
(isupper( c ) ? tolower( c ) : c) : c);
}
либо ...
putchar( isalpha(c) && isupper(c) ? tolower(c) : c ); либо даже
putchar( isupper(c) ? tolower(c) : c );
В последнем случае под isupper и islower должны пониматься только буквы (увы, не во всех реализациях это так!).
7.49. Обратите внимание, что если мы выделяем класс символов при помощи сравнения, например:
char ch;
if( 0300 <= ch && ch < 0340 ) ...;
(в кодировке КОИ-8 это маленькие русские буквы), то мы можем натолкнуться на следующий сюрприз: перед сравнением с целым значение ch приводится к типу int (приведение также делается при использовании char в качестве аргумента функции). При этом, если у ch был установлен старший бит (0200), произойдет расширение его во весь старший байт (расширение знакового бита). Результатом будет отрицательное целое число! Опыт:
char c = '\201'; |
/* = 129 */ |
printf( "%d\n", c ); |
|
печатается -127. Таким образом, наше сравнение не сработает, т.к. оказывается что ch < 0. Следует подавлять расширение знака:
if( 0300 <= (ch & 0377) && (ch & 0377) < 0340) ...;
(0377 - маска из 8 бит, она же 0xFF, весь байт), либо объявить
А. Богатырёв, 1992-96 |
- 276 - |
Си в UNIX™ |
unsigned char ch;
что означает, что при приведении к int знаковый бит не расширяется.
7.50. Рассмотрим еще один пример:
main(){ char ch;
/* 0377 - код последнего символа алфавита ASCII */ for (ch = 0100; ch <= 0377; ch++ )
printf( "%03o %s\n", ch & 0377,
ch >= 0300 && ch < 0340 ? "yes" : "no" );
}
Какие неприятности ждут нас здесь?
•во-первых, когда бит 0200 у ch установлен, в сравнении ch выступает как отрицательное целое число (т.к. приведение к int делается расширением знакового бита), то есть у нас всегда печатается "no". Это мы можем исправить, написав unsigned char ch, либо используя ch в виде
(ch & 0377) |
или |
((unsigned) ch) |
•во-вторых, рассмотрим сам цикл. Пусть сейчас ch =='\377'. Условие ch <= 0377 истинно. Выполняется оператор ch++. Но ch - это байт, поэтому операции над ним производятся по модулю 0400 (0377 - это максимальное значение, которое можно хранить в байте - все биты единицы). То есть теперь значе-
нием ch станет 0. Но 0 < 0377 и условие цикла верно! Цикл продолжается; т.е. происходит зацикливание. Избежать этого можно только описав int ch; чтобы 0377+1 было равно 0400, а не 0 (или unsigned int, лишь бы длины переменной хватало, чтобы вместить число больше 0377).
7.51. Составьте программу, преобразующую текст, состоящий только из строчных букв в текст, состоящий из прописных и строчных букв. Первая буква и буква после каждой точки - прописные, остальные - строчные.
слово один. слово два. --> Слово один. Слово два.
Эта программа может оказаться полезной для преобразования текста, набранного в одном регистре, в текст, содержащий буквы обоих регистров.
7.52. Напишите программу, исправляющую опечатки в словах (spell check): программе задан список слов; она проверяет - является ли введенное вами слово словом из списка. Если нет - пытается найти наиболее похожее слово из списка, причем если есть несколько похожих - выдает все варианты. Отлавливайте случаи:
•две соседние буквы переставлены местами: ножинцы=>ножницы;
•удвоенная буква (буквы): ккаррандаш=>карандаш;
•потеряна буква: бот=>болт;
•измененная буква: бинт=>бант;
•лишняя буква: морда=>мода;
•буквы не в том регистре - сравните с каждым словом из списка, приводя все буквы к маленьким:
сОВОк=>совок;
Надо проверять каждую букву слова. Возможно вам будет удобно использовать рекурсию. Подсказка: для некоторых проверок вам может помочь функция match:
слово_таблицы = "дом";
if(strlen(входное_слово) <= strlen(слово_таблицы)+1 && match(входное_слово, "*д*о*м*") ... /* похоже */
*о*м* |
?дом |
дом? |
*д*м* |
д?ом |
|
*д*о* |
до?м |
|
Приведем вариант решения этой задачи: