* на наличие лишних букв
*/
if(i==1) max = 4;
}
}
}
return total;
}
А. Богатырев, 1992-95 - 327 - Си в UNIX
void main(){
uchar inpbuf[BUFSIZ];
int n;
uchar *reply, **ptr;
setlocale(LC_ALL, "");
for(ptr = words; *ptr; ptr++)
printf("#\t%s\n", *ptr);
do{
printf("> "); fflush(stdout);
if(gets((char *)inpbuf) == NULL) break;
switch(spellmatch(inpbuf, words, &reply)){
case -1:
printf("Нет такого слова\n"); break;
case 0:
printf("Слово '%s'\n", reply); break;
default:
printf("Неоднозначно\n");
}
} while(1);
}
7.53. Пока я сам писал эту программу, я сделал две ошибки, которые должны быть
весьма характерны для новичков. Про них надо бы говорить раньше, в главе про строки и
в самой первой главе, но тут они пришлись как раз к месту. Вопрос: что печатает сле-
дующая программа?
#include
char *strings[] = {
"Первая строка"
"Вторая строка"
"Третяя строка",
"Четвертая строка",
NULL
};
void main(){
char **p;
for(p=strings;*p;++p)
printf("%s\n", *p);
}
А печатает она вот что:
Первая строкаВторая строкаТретяя строка
Четвертая строка
Дело в том, что ANSI компилятор Си склеивает строки:
"начало строки" "и ее конец"
если они разделены пробелами в смысле isspace, в том числе и пустыми строками. А в
нашем объявлении массива строк strings мы потеряли несколько разделительных запятых!
Вторая ошибка касается того, что можно забыть поставить слово break в операторе
switch, и долго после этого гадать о непредсказуемом поведении любого поступающего на
вход значения. Дело просто: пробегаются все случаи, управление проваливается из case
в следующий case, и так много раз подряд! Это и есть причина того, что в предыдущем
А. Богатырев, 1992-95 - 328 - Си в UNIX
примере все case оформлены нетривиальным макросом Bcase.
7.54. Составьте программу кодировки и раскодировки файлов по заданному ключу (строке
символов).
7.55. Составьте программу, которая запрашивает анкетные данные типа фамилии, имени,
отчества, даты рождения и формирует файл. Программа должна отлавливать ошибки ввода
несимвольной и нецифровой информации, выхода составляющих даты рождения за допустимые
границы с выдачей сообщений об ошибках. Программа должна давать возможность корректи-
ровать вводимые данные. Все данные об одном человеке записываются в одну строку файла
через пробел. Вот возможный пример части диалога (ответы пользователя выделены
жирно):
Введите месяц рождения [1-12]: 14 <<ENTER>>
*** Неправильный номер месяца (14).
Введите месяц рождения [1-12]: март <<ENTER>>
*** Номер месяца содержит букву 'м'.
Введите месяц рождения [1-12]: <<ENTER>>
Вы хотите закончить ввод ? n
Введите месяц рождения [1-12]: 11 <<ENTER>>
Ноябрь
Введите дату рождения [1-30]: _
В таких программах обычно ответ пользователя вводится как строка:
printf("Введите месяц рождения [1-12]: ");
fflush(stdout); gets(input_string);
затем (если надо) отбрасываются лишние пробелы в начале и в конце строки, затем вве-
денный текст input_string анализируется на допустимость символов (нет ли в нем не
цифр?), затем строка преобразуется к нужному типу (например, при помощи функции atoi
переводится в целое) и проверяется допустимость полученного значения, и.т.д.
Вводимую информацию сначала заносите в структуру; затем записывайте содержимое
полей структуры в файл в текстовом виде (используйте функцию fprintf, а не fwrite).
7.56. Составьте программу, осуществляющую выборку информации из файла, сформирован-
ного в предыдущей задаче, и ее распечатку в табличном виде. Выборка должна осуществ-
ляться по значению любого заданного поля (т.е. вы выбираете поле, задаете его значе-
ние и получаете те строки, в которых значение указанного поля совпадает с заказанным
вами значением). Усложнение: используйте функцию сравнения строки с регулярным выра-
жением для выборки по шаблону поля (т.е. отбираются только те строки, в которых зна-
чение заданного поля удовлетворяет шаблону). Для чтения файла используйте fscanf,
либо fgets и затем sscanf. Второй способ лучше тем, что позволяет проверить по шаб-
лону значение любого поля - не только текстового, но и числового: так 1234 (строка -
изображение числа) удовлетворяет шаблону "12*".
7.57. Составьте вариант программы подсчета служебных слов языка Си, не учитывающий
появление этих слов, заключенных в кавычки.
7.58. Составьте программу удаления из программы на языке Си всех комментариев. Обра-
тите внимание на особые случаи со строками в кавычках и символьными константами; так
строка
char s[] = "/*";
не является началом комментария! Комментарии записывайте в отдельный файл.
7.59. Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выво-
дит список всех идентификаторов переменных, используемых в программе, и для каждого
из идентификаторов выводит список номеров строк, в которые он входит.
А. Богатырев, 1992-95 - 329 - Си в UNIX
7.60. Разработайте простую версию препроцессора для обработки операторов #include.
В качестве прототипа такой программы можно рассматривать такую (она понимает дирек-
тивы вида #include имяфайла - без <> или "").
#include
#include
#include
char KEYWORD[] = "#include "; /* with a trailing space char */
void process(char *name, char *from){
FILE *fp;
char buf[4096];
if((fp = fopen(name, "r")) == NULL){
fprintf(stderr, "%s: cannot read \"%s\", %s\n",
from, name, strerror(errno));
return;
}
while(fgets(buf, sizeof buf, fp) != NULL){
if(!strncmp(buf, KEYWORD, sizeof KEYWORD - 1)){
char *s;
if((s = strchr(buf, '\n')) != NULL) *s = '\0';
fprintf(stderr, "%s: including %s\n",
name, s = buf + sizeof KEYWORD - 1);
process(s, name);
} else fputs(buf, stdout);
}
fclose(fp);
}
int main(int ac, char *av[]){
int i;
for(i=1; i < ac; i++)
process(av[i], "MAIN");
return 0;
}
7.61. Разработайте простую версию препроцессора для обработки операторов #define.
Сначала реализуйте макросы без аргументов. Напишите обработчик макросов вида
#macro имя(аргу,менты)
тело макроса - можно несколько строк
#endm
7.62. Напишите программу, обрабатывающую определения #ifdef, #else, #endif. Учтите,
что эти директивы могут быть вложенными:
#ifdef A
# ifdef B
... /* defined(A) && defined(B) */
# endif /*B*/
... /* defined(A) */
#else /*not A*/
... /* !defined(A) */
# ifdef C
... /* !defined(A) && defined(C) */
# endif /*C*/
А. Богатырев, 1992-95 - 330 - Си в UNIX
#endif /*A*/
7.63. Составьте программу моделирования простейшего калькулятора, который считывает
в каждой строчке по одному числу (возможно со знаком) или по одной операции сложения
или умножения, осуществляет операцию и выдает результат.
7.64. Составьте программу-калькулятор, которая производит операции сложения, вычита-
ния, умножения, деления; операнды и знак арифметической операции являются строковыми
аргументами функции main.
7.65. Составьте программу, вычисляющую значение командной строки, представляющей
собой обратную польскую запись арифметического выражения. Например, 20 10 5 + *
вычисляется как 20 * (10 + 5) .
7.66. Составьте функции работы со стеком:
- добавление в стек
- удаление вершины стека (с возвратом удаленного значения)
Используйте два варианта: стек-массив и стек-список.
7.67. Составьте программу, которая использует функции работы со стеком для перевода
арифметических выражений языка Си в обратную польскую запись.
/*#!/bin/cc $* -lm
* Калькулятор. Иллюстрация алгоритма превращения выражений
* в польскую запись по методу приоритетов.
*/
#include
#include /* extern double atof(); */
#include /* extern double sin(), ... */
#include /* isdigit(), isalpha(), ... */
#include /* jmp_buf */
jmp_buf AGAIN; /* контрольная точка */
err(n){ longjmp(AGAIN,n);} /* прыгнуть в контрольную точку */
А. Богатырев, 1992-95 - 331 - Си в UNIX
/* ВЫЧИСЛИТЕЛЬ --------------------------------------- */
/* Если вместо помещения операндов в стек stk[] просто
* печатать операнды, а вместо выполнения операций над
* стеком просто печатать операции, мы получим "польскую"
* запись выражения:
* a+b -> a b +
* (a+b)*c -> a b + c *
* a + b*c -> a b c * +
*/
/* стек вычислений */
#define MAXDEPTH 20 /* глубина стеков */
int sp; /* указатель стека (stack pointer) */
double stk[MAXDEPTH];
double dpush(d) double d; /* занести число в стек */
{
if( sp == MAXDEPTH ){ printf("Стек операндов полон\n");err(1);}
else return( stk[sp++] = d );
}
double dpop(){ /* взять вершину стека */
if( !sp ){ printf("Стек операндов пуст\n"); err(2); }
else return stk[--sp];
}
static double r,p; /* вспомогательные регистры */
void add() { dpush( dpop() + dpop()); }
void mult() { dpush( dpop() * dpop()); }
void sub() { r = dpop(); dpush( dpop() - r); }
void divide() { r = dpop();
if(r == 0.0){ printf("Деление на 0\n"); err(3); }
dpush( dpop() / r );
}
void pwr() { r = dpop(); dpush( pow( dpop(), r )); }
void dup() { dpush( dpush( dpop())); }
void xchg(){ r = dpop(); p = dpop(); dpush(r); dpush(p); }
void neg() { dpush( - dpop()); }
void dsin(){ dpush( sin( dpop())); }
void dcos(){ dpush( cos( dpop())); }
void dexp(){ dpush( exp( dpop())); }
void dlog(){ dpush( log( dpop())); }
void dsqrt(){ dpush( sqrt( dpop())); }
void dsqr(){ dup(); mult(); }
/* M_PI и M_E определены в */
void pi() { dpush( M_PI /* число пи */ ); }
void e() { dpush( M_E /* число e */ ); }
void prn() { printf("%g\n", dpush( dpop())); }
void printstk(){
if( !sp ){ printf("Стек операндов пуст\n"); err(4);}
while(sp) printf("%g ", dpop());
putchar('\n');
}
А. Богатырев, 1992-95 - 332 - Си в UNIX
/* КОМПИЛЯТОР ---------------------------------------- */
/* номера лексем */
#define END (-3) /* = */