Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 1992-96 |
- 281 - |
Си в UNIX™ |
7.53. Пока я сам писал эту программу, я сделал две ошибки, которые должны быть весьма характерны для новичков. Про них надо бы говорить раньше, в главе про строки и в самой первой главе, но тут они пришлись как раз к месту. Вопрос: что печатает следующая программа?
#include <stdio.h>
char *strings[] = { "Первая строка" "Вторая строка" "Третяя строка", "Четвертая строка", NULL
};
void main(){ char **p;
for(p=strings;*p;++p) printf("%s\n", *p);
}
А печатает она вот что:
Первая строкаВторая строкаТретяя строка Четвертая строка
Дело в том, что ANSI компилятор Си склеивает строки:
"начало строки" "и ее конец"
если они разделены пробелами в смысле isspace, в том числе и пустыми строками. А в нашем объявлении массива строк strings мы потеряли несколько разделительных запятых!
Вторая ошибка касается того, что можно забыть поставить слово break в операторе switch, и долго после этого гадать о непредсказуемом поведении любого поступающего на вход значения. Дело просто: пробегаются все случаи, управление проваливается из case в следующий case, и так много раз подряд! Это и есть причина того, что в предыдущем примере все 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. Составьте программу, осуществляющую выборку информации из файла, сформированного в предыдущей задаче, и ее распечатку в табличном виде. Выборка должна осуществляться по значению любого заданного поля (т.е. вы выбираете поле, задаете его значение и получаете те строки, в которых значение
А. Богатырёв, 1992-96 |
- 282 - |
Си в UNIX™ |
указанного поля совпадает с заказанным вами значением). Усложнение: используйте функцию сравнения строки с регулярным выражением для выборки по шаблону поля (т.е. отбираются только те строки, в которых значение заданного поля удовлетворяет шаблону). Для чтения файла используйте fscanf, либо fgets и затем sscanf. Второй способ лучше тем, что позволяет проверить по шаблону значение любого поля - не только текстового, но и числового: так 1234 (строка - изображение числа) удовлетворяет шаблону "12*".
7.57.Составьте вариант программы подсчета служебных слов языка Си, не учитывающий появление этих слов, заключенных в кавычки.
7.58.Составьте программу удаления из программы на языке Си всех комментариев. Обратите внимание на особые случаи со строками в кавычках и символьными константами; так строка
char s[] = "/*";
не является началом комментария! Комментарии записывайте в отдельный файл.
7.59.Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выводит список всех идентификаторов переменных, используемых в программе, и для каждого из идентификаторов выводит список номеров строк, в которые он входит.
7.60.Разработайте простую версию препроцессора для обработки операторов #include. В качестве прототипа такой программы можно рассматривать такую (она понимает директивы вида #include имяфайла - без <> или "").
#include <stdio.h> #include <string.h> #include <errno.h>
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
А. Богатырёв, 1992-96 |
- 283 - |
Си в UNIX™ |
|
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*/ |
|
|
|
#endif |
/*A*/ |
|
|
7.63.Составьте программу моделирования простейшего калькулятора, который считывает в каждой строчке по одному числу (возможно со знаком) или по одной операции сложения или умножения, осуществляет операцию и выдает результат.
7.64.Составьте программу-калькулятор, которая производит операции сложения, вычитания, умножения, деления; операнды и знак арифметической операции являются строковыми аргументами функции main.
7.65.Составьте программу, вычисляющую значение командной строки, представляющей собой обратную польскую запись арифметического выражения. Например, 20 10 5 + * вычисляется как 20 * (10 + 5) .
7.66.Составьте функции работы со стеком:
•добавление в стек
•удаление вершины стека (с возвратом удаленного значения)
Используйте два варианта: стек-массив и стек-список.
7.67. Составьте программу, которая использует функции работы со стеком для перевода арифметических выражений языка Си в обратную польскую запись.
/*#!/bin/cc $* -lm
*Калькулятор. Иллюстрация алгоритма превращения выражений
*в польскую запись по методу приоритетов.
*/ |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> /* extern double atof(); |
*/ |
|
#include <math.h> |
/* extern double sin(), ... |
*/ |
#include <ctype.h> |
/* isdigit(), isalpha(), ... |
*/ |
#include <setjmp.h> |
/* jmp_buf |
*/ |
jmp_buf AGAIN; |
/* контрольная точка */ |
|
err(n){ longjmp(AGAIN,n);} /* прыгнуть в контрольную точку */
А. Богатырёв, 1992-96 |
- 289 - |
Си в UNIX™ |
/* прочесть открывающую скобку после имени функции */ void getbrack(){
int c;
while((c = getchar()) != EOF && c != '(' ) if( !isspace(c) && c != '\n' ){
printf("Между именем функции %s и ( символ %c\n", lastf, c); ungetc(c, stdin); err(12);
}
}
void main(){ calc();}
/* Примеры:
( sin( pi() / 4 + 0.1 ) + sum(2, 4 + 1)) * (5 - 4/2) = ответ: 23.3225
(14 + 2 ** 3 * 7 + 2 * cos(0)) / ( 7 - 4 ) = ответ: 24
*/
7.68. Приведем еще один арифметический вычислитель, использующий классический рекурсивный подход:
/* Калькулятор на основе рекурсивного грамматического разбора.
*По мотивам арифметической части программы csh (СиШелл).
*csh написан Биллом Джоем (Bill Joy).
: var1 = (x = 1+3) * (y=x + x++) |
36 |
: s = s + 1 |
ошибка |
: y |
9 |
: s = (1 + 1 << 2) == 1 + (1<<2) |
0 |
: var1 + 3 + -77 |
-38 |
: a1 = 3; a2 = (a4=a3 = 2; a1++)+a4+2 |
8 |
: sum(a=2;b=3, a++, a*3-b) |
12 |
*/
#include <stdio.h> #include <ctype.h> #include <setjmp.h>
typedef enum { NUM, ID, OP, OPEN, CLOSE, UNKNOWN, COMMA, SMC } TokenType;
char *toknames[] = { "number", "identifier", "operation", "open_paren", "close_paren", "unknown", "comma", "semicolon" };
typedef struct _Token |
{ |
|
|
char *token; |
|
/* лексема (слово) |
*/ |
struct _Token |
*next; |
/* ссылка на следующую */ |
|
TokenType type; |
/* тип лексемы |
*/ |
|
} Token; |
|
|
|
extern void *malloc(unsigned); extern char *strchr(char *, char);
char *strdup(const char *s){
char *p = (char *)malloc(strlen(s)+1); if(p) strcpy(p,s); return p;
}
/* Лексический разбор ------------------------------------------*/ /* Очистить цепочку токенов */
void freelex(Token **p){ Token *thisTok = *p;
while( thisTok ){ Token *nextTok = thisTok->next; free((char *) thisTok->token); free((char *) thisTok); thisTok = nextTok;
}
*p = NULL;
}