русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Складні оголошення


Дата додавання: 2014-11-28; переглядів: 960.


На мову C подеколи нарікають за синтаксис її оголошень, особливо тих, що стосуються покажчиків на функції. Цей синтаксис являється спробою узгодити оголошення із використанням; він добре працює в простих випадках, але може заплутати в складніших, так як оголошення не можна читати з права на ліво і часом дужок занадто багато. Різниця між

int *f(); /* f: функція, що повертає покажчик на int */

і

int (*pf)(); /* pf: покажчик на функцію, що повертає int */

яскраво ілюструє цю проблему: * — це префіксний оператор і має нижчий пріоритет за (), тож дужки необхідні, щоб забезпечити відповідний зв'язок.

Хоча дійсно складні оголошення рідко зустрічаються на практиці, важливо знати як їх зрозуміти і, коли треба, як створити їх. Один з непоганих шляхів синтезувати оголошення, це за допомогою невеличких кроків із typedef, розглянутого в Розділі 6.7. Як альтернатива, в цьому розділі ми представимо пару програм, що перекладають з чинної C на людську мову, а потім назад. Словесний опис можна прочитати зліва направо.

Перша, dcl, дещо складніша. Вона перекладає оголошення C у слова, як, скажімо,

char **argv

argv: pointer to char

int (*daytab)[13]

daytab: pointer to array[13] of int

int *daytab[13]

daytab: array[13] of pointer to int

void *comp()

comp: function returning pointer to void

void (*comp)()

comp: pointer to function returning void

char (*(*x())[])()

x: function returning pointer to array[] of

pointer to function returning char

char (*(*x[3])())[5]

x: array[3] of pointer to function returning

pointer to array[5] of char

dcl основано на граматиці оголошень, яку точно описано в Додатку А, Розділі 8.5; ось спрощена форма:

оголошувач: необов'язкові * прямий-оголошувач
прямий-оголошувач: назва
  (оголошувач)
прямий-оголошувач()  
  прямий-оголошувач[необов'язковий розмір]

тобто, оголошувач — це прямий-оголошувач, можливо із попередніми *. прямий-оголошувач — це або назва, або внесений в дужки оголошувач, або прямий-оголошувач із наступними дужками, або прямий-оголошувач з квадратними дужками і необов'язковим розміром.

Ця граматика може бути використана для аналізу оголошень. Наприклад, розглянемо оголошення

(*pfa[])()

де pfa буде ідентифіковано як назву, а тому як прямий-оголошувач. Тоді pfa[] — це такожпрямий-оголошувач. Після цього *pfa[] розпізнано як оголошувач, тож (*pfa[]) є прямим-оголошувачем. Потім (*pfa[])() розпізнано як прямий-оголошувач, а тому як оголошувач. Ми можемо зобразити цей аналіз графічно як дерево (прямий-оголошувач скорочено до пр-ог):

( * pfa [] ) ()

\ \ | / / /

\ \ назва / / /

\ \ | / / /

\ \ пр-ог / / /

\ \ | / / /

\ \ пр-ог / /

\ \ | / /

\ ог / /

\ | / /

пр-ог /

| /

пр-ог

|

ог

Основою програми dcl є пара функцій — dcl і dirdcl — котрі обробляють оголошення відповідно до цієї граматики. Оскільки граматика означена рекурсивно, функції викликають одна одну рекурсивно по мірі того, як вони розпізнають частини оголошення; програма називається рекурсивно-спадним оброблювачем.

/* dcl: прочитує оголошувач */

void dcl(void)

{

int ns;

 

for (ns = 0; gettoken() == '*'; ) /* count *'s */

ns++;

dirdcl();

while (ns-- > 0)

strcat(out, " pointer to");

}

 

/* dirdcl: прочитує безпосередній оголошувач */

void dirdcl(void)

{

int type;

 

if (tokentype == '(') { /* ( dcl ) */

dcl();

if (tokentype != ')')

printf("error: missing )\n");

} else if (tokentype == NAME) /* variable name */

strcpy(name, token);

else

printf("error: expected name or (dcl)\n");

while ((type=gettoken()) == PARENS || type == BRACKETS)

 

if (type == PARENS)

strcat(out, " function returning");

else {

strcat(out, " array");

strcat(out, token);

strcat(out, " of");

}

}

Ми хотіли, щоб програма була ілюстративною, а не куленепробивною, тому dcl багато в чому обмежена. Вона розуміє записи лише простих типів даних, таких як char або int. Вона не розуміє типи аргументів функцій або класифікатори на кшталт const. Випадкові пробіли заплутують її. Вона не здійснює якихось спроб обробки помилок, тож неправильні оголошення також заплутають її. Покращення цієї програми ми залишаємо вам як вправа.

Ось глобальні змінні і функція main:

#include <stdio.h>

#include <string.h>

#include <ctype.h>

 

#define MAXTOKEN 100

 

enum { NAME, PARENS, BRACKETS };

 

void dcl(void);

void dirdcl(void);

 

int gettoken(void);

int tokentype; /* тип останньої лексеми */

char token[MAXTOKEN]; /* ланцюжок останньої лексеми */

char name[MAXTOKEN]; /* назва ідентифікатору */

char datatype[MAXTOKEN]; /* тип даних = char, int тощо */

char out[1000];

 

main() /* перетворює оголошення на словесний опис */

{

while (gettoken() != EOF) { /* перша лексема рядка */

strcpy(datatype, token); /* is the datatype */

out[0] = '\0';

dcl(); /* читання решти рядка */

if (tokentype != '\n')

printf("syntax error\n");

printf("%s: %s %s\n", name, out, datatype);

}

return 0;

}

Функція gettoken опускає пробіли і табуляцію, після чого знаходить наступну лексему у вводі; лексемою може бути ім'я, пара дужок, пара квадратних дужок із можливим числом всередині, або будь-який інший одиничний знак.

int gettoken(void) /* return next token */

{

int c, getch(void);

void ungetch(int);

char *p = token;

 

while ((c = getch()) == ' ' || c == '\t')

;

if (c == '(') {

if ((c = getch()) == ')') {

strcpy(token, "()");

return tokentype = PARENS;

} else {

ungetch(c);

return tokentype = '(';

}

} else if (c == '[') {

for (*p++ = c; (*p++ = getch()) != ']'; )

;

*p = '\0';

return tokentype = BRACKETS;

} else if (isalpha(c)) {

for (*p++ = c; isalnum(c = getch()); )

*p++ = c;

*p = '\0';

ungetch(c);

return tokentype = NAME;

} else

return tokentype = c;

 

}

getch і ungetch обговорювались у Розділі 4.

Протилежний напрямок є легшим, особливо якщо ми не хвилюватимемось про створення зайвих дужок. Програма undcl перетворює словесний опис на зразок «x is a function returning a pointer to an array of pointers to functions returning char», який ми виразимо як

x () * [] * () char

на

char (*(*x())[])()

Скорочений синтаксис вводу дозволяє нам повторне використання функції gettoken. undclтакож використовує ті самі зовнішні змінні що й dcl.

/* undcl: перетворює словесний опис на оголошення */

main()

{

int type;

char temp[MAXTOKEN];

 

while (gettoken() != EOF) {

strcpy(out, token);

while ((type = gettoken()) != '\n')

if (type == PARENS || type == BRACKETS)

strcat(out, token);

else if (type == '*') {

sprintf(temp, "(*%s)", out);

strcpy(out, temp);

} else if (type == NAME) {

sprintf(temp, "%s %s", token, out);

strcpy(out, temp);

} else

printf("invalid input at %s\n", token);

}

return 0;

}

Вправа 5-18. Додайте перевірку на помилки і відновлення після помилок до dcl.

Вправа 5-19. Змініть undcl таким чином, щоб вона не додавала зайві дужки до оголошень.

Вправа 5-20. Розширте dcl, щоб вона обробляла оголошення з типами аргументів функцій, класифікатори на зразок const тощо.


<== попередня лекція | наступна лекція ==>
Покажчики на функції | Основні поняття про структури


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн