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

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

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

А. Богатырёв, 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

- 284 -

Си в 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 определены в <math.h> */

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

- 285 -

Си в UNIX™

/* КОМПИЛЯТОР ----------------------------------------

 

 

 

*/

/* номера лексем */

 

 

 

#define END

(-3)

/* = */

#define NUMBER

(-2)

/* число */

#define BOTTOM

0

 

/* псевдолексема "дно стека" */

#define OPENBRACKET

1

/* (

*/

#define FUNC

 

2

/* f( */

#define CLOSEBRACKET

3

/* )

*/

#define COMMA

 

4

/* ,

*/

#define PLUS

 

5

/* +

*/

#define MINUS

 

6

/* -

*/

#define MULT

 

7

/* *

*/

#define DIV

 

8

/* /

*/

#define POWER

 

9

/* ** */

/* Приоритеты */

 

 

 

 

#define NOTDEF

333

 

/* не определен */

#define INFINITY 3000

 

/* бесконечность */

/* Стек транслятора */

 

 

 

typedef struct _opstack

{

 

int cop;

 

 

/* код операции */

void (*f)();

 

/* "отложенное" действие */

} opstack;

 

 

 

 

int osp;

/* operations stack pointer */

opstack ost[MAXDEPTH];

void push(n, func) void (*func)();

{

if(osp == MAXDEPTH){ printf("Стек операций полон\n");err(5);} ost[osp].cop = n; ost[osp++].f = func;

}

int pop(){

if( !osp ){ printf("Стек операций пуст\n"); err(6); } return ost[--osp].cop;

}

int top(){

if( !osp ){ printf("Стек операций пуст\n"); err(7); } return ost[osp-1].cop;

}

void (*topf())(){ return ost[osp-1].f;

}

 

#define drop()

(void)pop()

void nop(){ printf( "???\n" ); } /* no operation */ void obr_err(){ printf( "Не хватает )\n" ); err(8); }

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

- 286 -

Си в UNIX™

/* Таблица приоритетов */

 

struct synt{

 

 

int inp_prt;

/* входной приоритет

*/

int stk_prt;

/* стековый приоритет

*/

void (*op)();

/* действие над стеком вычислений */

} ops[] = {

 

 

 

 

 

 

/* BOTTOM

*/

{NOTDEF, -1,

nop

},

 

/* OPENBRACKET

*/

{INFINITY,

0,

obr_err},

 

/* FUNC

*/

{INFINITY,

0,

obr_err},

 

/* CLOSEBRACKET */

{1,

NOTDEF,

nop

},

/* NOPUSH */

/* COMMA

*/

{1,

NOTDEF,

nop

},

/* NOPUSH */

/* PLUS

*/

{1,

1,

add

},

 

/* MINUS

*/

{1,

1,

sub

},

 

/* MULT

*/

{2,

2,

mult

},

 

/* DIV

*/

{2,

2,

divide

},

 

/* POWER

*/

{3,

3,

pwr

}

 

};

 

 

 

 

 

 

#define stkprt(i)

 

ops[i].stk_prt

 

 

 

#define inpprt(i)

 

ops[i].inp_prt

 

 

 

#define perform(i) (*ops[i].op)()

/* значения, заполняемые лексическим анализатором */ double value; void (*fvalue)();

int tprev; /* предыдущая лексема */

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

- 287 -

Си в UNIX™

/* Транслятор в польскую запись + интерпретатор */

void reset(){ sp = osp = 0; push(BOTTOM, NULL); tprev = END;} void calc(){

int t; do{

if( setjmp(AGAIN))

printf( "Стеки после ошибки сброшены\n" );

reset();

while((t = token()) != EOF && t != END){ if(t == NUMBER){

if(tprev == NUMBER){

printf("%g:Два числа подряд\n",value); err(9);

}

/* любое число просто заносится в стек */ tprev = t; dpush(value); continue;

}

/* иначе - оператор */ tprev = t;

/* Выталкивание и выполнение операций со стека */

 

while(inpprt(t) <= stkprt( top()) )

 

 

perform( pop());

 

/* Сокращение или подмена скобок */

 

 

if(t == CLOSEBRACKET){

 

 

 

if( top() == OPENBRACKET || top() == FUNC ){

 

 

void (*ff)() = topf();

 

 

drop(); /* схлопнуть скобки */

 

 

/* обработка функции */

 

 

if(ff)

(*ff)();

 

 

}else{ printf( "Не хватает (\n"); err(10); }

 

}

 

 

/* Занесение операций в стек (кроме NOPUSH-операций) */

 

if(t != CLOSEBRACKET && t != COMMA)

 

 

push(t, t == FUNC ? fvalue : NULL );

}

 

 

 

if( t != EOF ){

 

 

/* Довыполнить оставшиеся операции */

 

while( top() != BOTTOM )

 

 

perform( pop());

 

 

printstk();

/* печать стека вычислений (ответ) */

}

 

 

 

} while (t != EOF);

 

 

}

 

 

 

/* Лексический анализатор ---------------------------- */

extern void getn(), getid(), getbrack();

 

int token(){

/* прочесть лексему */

 

int c;

while((c = getchar())!= EOF && (isspace(c) || c == '\n')); if(c == EOF) return EOF;

ungetc(c, stdin);

if(isdigit(c)){ getn(); return NUMBER; } if(isalpha(c)){ getid(); getbrack(); return FUNC; } return getop();

}

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

- 288 -

Си в UNIX™

/* Прочесть число (с точкой) */

 

void getn(){

 

int c, i; char s[80];

 

s[0] = getchar();

 

for(i=1; isdigit(c = getchar()); i++ )

s[i] = c;

if(c == '.'){ /* дробная часть */

 

s[i] = c;

 

for(i++; isdigit(c = getchar()); i++)

s[i] = c;

}

s[i] = '\0'; ungetc(c, stdin); value = atof(s);

}

 

 

 

 

 

/* Прочесть операцию */

 

 

 

int getop(){

 

 

 

 

 

int c;

 

 

 

 

 

switch( c = getchar()){

 

 

case EOF:

 

return EOF;

 

case '=':

 

return END;

 

case '+':

 

return PLUS;

 

case '-':

 

return MINUS;

 

case '/':

 

return DIV;

 

case '*':

 

c = getchar();

 

 

 

 

if(c == '*') return POWER;

 

 

 

else{ ungetc(c, stdin); return MULT; }

case '(':

 

return OPENBRACKET;

case ')':

 

return CLOSEBRACKET;

case ',':

 

return COMMA;

 

default:

 

 

printf( "Ошибочная операция %c\n", c);

 

 

 

return token();

 

}

 

 

 

 

 

}

 

 

 

 

 

struct funcs{

/* Таблица имен функций */

char *fname; void (*fcall)();

 

} tbl[] = {

 

 

 

 

 

{ "sin", dsin },

{ "cos",

dcos

},

{ "exp", dexp },

{ "sqrt",

dsqrt },

{ "sqr", dsqr },

{ "pi",

pi

},

{ "sum",

add

},

{ "ln",

dlog

},

{ "e",

e

},

{ NULL,

NULL

}

};

 

 

 

 

 

char *lastf;

/* имя найденной функции */

/* Прочесть имя функции

*/

 

 

void getid(){

 

 

 

 

 

struct funcs *ptr = tbl; char name[80]; int c, i; *name = getchar();

for(i=1; isalpha(c = getchar()); i++) name[i] = c; name[i] = '\0'; ungetc(c, stdin);

/* поиск в таблице */

for( ; ptr->fname; ptr++ )

if( !strcmp(ptr->fname, name)){ fvalue = ptr->fcall;

lastf = ptr->fname; return;

}

printf( "Функция \"%s\" неизвестна\n", name ); err(11);

}

А. Богатырёв, 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;

}

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

- 290 -

Си в UNIX™

/* Добавить токен в хвост списка */

void addtoken(Token **hd, Token **tl, char s[], TokenType t){ Token *newTok = (Token *) malloc(sizeof(Token)); newTok->next = (Token *) NULL;

newTok->token = strdup(s); newTok->type = t; if(*hd == NULL) *hd = *tl = newTok;

else{ (*tl)->next = newTok; *tl = newTok; }

}

/* Разобрать строку в список лексем (токенов) */ #define opsym(c) ((c) && strchr("+-=!~^|&*/%<>", (c))) #define is_alpha(c) (isalpha(c) || (c) == '_') #define is_alnum(c) (isalnum(c) || (c) == '_')

void lex(Token **hd, Token **tl, register char *s){ char *p, csave; TokenType type;

while(*s){

while( isspace(*s)) ++s; p = s; if( !*s ) break;

if(isdigit (*s)){ type = NUM; while(isdigit (*s))s++; }

else if(is_alpha(*s)){ type = ID;

while(is_alnum(*s))s++; }

else if(*s == '('){

type = OPEN;

s++; }

 

else if(*s == ')'){

type = CLOSE; s++; }

 

else if(*s == ','){

type = COMMA; s++; }

 

else if(*s == ';'){

type = SMC;

s++; }

 

else if(opsym(*s)){

type = OP;

while(opsym(*s))

s++; }

else {

 

type = UNKNOWN;

s++; }

csave = *s; *s = '\0'; addtoken(hd, tl, p, type); *s = csave;

}

 

 

 

 

}

 

 

 

 

/* Распечатка списка лексем */

 

 

 

void printlex(char *msg, Token *t){

 

 

if(msg && *msg) printf("%s: ", msg);

 

 

for(; t != NULL; t = t->next)

 

 

printf("%s`%s' ", toknames[(int)t->type], t->token);

 

putchar('\n');

 

 

 

 

}

 

 

 

 

/* Система переменных ----------------------------------------- */

#define NEXT(v)

*v = (*v)->next

 

 

#define TOKEN(v)

(*v)->token

 

 

#define TYPE(v)

(*v)->type

 

 

#define eq(str1, str2)

(!strcmp(str1, str2))

 

 

jmp_buf breakpoint;

#define ERR(msg,val) { printf("%s\n", msg);longjmp(breakpoint, val+1);}

typedef struct {

 

 

char *name;

/* Имя переменной

*/

int value;

/* Значение переменной */

int isset;

/* Получила ли значение ? */

} Var;

 

 

#define MAXV 40 Var vars[MAXV];

/* Получить значение переменной */ int getVar(char *name){ Var *ptr; for(ptr=vars; ptr->name; ptr++)

if(eq(name, ptr->name)){ if(ptr->isset) return ptr->value;

printf("%s: ", name); ERR("variable is unbound yet", 0);

}

printf("%s: ", name); ERR("undefined variable", 0);

}