The C Programming Language(第 2 版) 笔记 / 5 指针与数组 / 5.12 复杂声明

目录、参考文献


5.12 复杂声明

C 语言常常因为声明的语法问题而受到人们的批评,特别是涉及到函数指针的语法
C 语言的语法力图使声明和使用相一致
对于简单的情况,C 语言的做法是很有效的,但是,如果情况比较复杂,则容易让人混淆
原因在于,C 语言的声明不能从左至右阅读,而且使用了太多的圆括号
我们来看下面所示的两个声明:

int *f(); /* f: function returning pointer to int */

以及

int (*pf)(); /* pf: pointer to function returning 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 是基于声明符的语法编写的
附录 A 以及 8.5 节将对声明符的语法进行详细的描述
下面是其简化的语法形式:

dcl:    optional *'s direct-dcl 
direct-dcl name 
                (dcl) 
                direct-dcl() 
                direct-dcl[optional size]

简而言之,声明符dcl)就是前面可能带有多个 *直接声明符direct-dcl
direct-dcl 可以是 name、由一对圆括号括起来的 dcl
后面跟有一对圆括号的 direct-dcl、后面跟着用方括号括起来的表示可选长度的 direct-dcl

该语法可用来对 C 语言的声明进行分析
例如声明符 (*pfa[])()
按照该语法分析,pfa 将被识别为一个 name,从而被认为是一个 direct-dcl
于是,pfa[] 也是一个 direct-dcl
接着,*pfa[] 被识别为一个 dcl,因此,判定 (*pfa[]) 是一个 direct-dcl
再接着,(*pfa[])() 被识别为一个 direct-dcl,因此也是一个 dcl
可以用下图所示的语法分析树来说明分析的过程(其中 direct-dcl 缩写为 dir-dcl

5-12

程序 dcl 的核心是两个函数:dcldirdcl,它们根据声明符的语法对声明进行分析
因为语法是递归定义的,所以在识别一个声明的组成部分时,这两个函数是相互递归调用的
我们称该程序是一个递归下降语法分析程序

/* dcl: parse a declarator */ 
void dcl(void) 
{
    int ns; 
    for (ns = 0; gettoken() == '*'; ) /* count *'s */ 
        ns++; 
    dirdcl(); 
    while (ns-- > 0) 
        strcat(out, " pointer to"); 
}

/* dirdcl: parse a direct declarator */ 
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 有很多限制
它只能处理类似于 charint 这样的简单数据类型,而无法处理函数中的参数类型或类似于 const 这样的限定符
它不能处理带有不必要空格的情况
由于没有完备的出错处理,因此它也无法处理无效的声明

下面是该程序的全局变量和主程序:

#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; /* type of last token */ 
char token[MAXTOKEN]; /* last token string */ 
char name[MAXTOKEN]; /* identifier name */ 
char datatype[MAXTOKEN]; /* data type = char, int, etc. */ 
char out[1000]; 

main() /* convert declaration to words */ 
{ 
    while (gettoken() != EOF) { /* 1st token on line */ 
        strcpy(datatype, token); /* is the datatype */ 
        out[0] = '\0'; 
        dcl(); /* parse rest of line */ 
        if (tokentype != '\n') 
            printf("syntax error\n"); 
        printf("%s: %s %s\n", name, out, datatype); 
    } 
    return 0; 
}

函数 gettoken 用来跳过空格与制表符,以查找输入中的下一个记号
“记号”(token)可以是一个名字,一对圆括号,可能包含一个数字的一对方括号,也可以是其它任何单个字符

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; 
}

有关函数 getchungetch 的说明,参见第 4 章

如果不在乎生成多余的圆括号,另一个方向的转换要容易一些
为了简化程序的输入,我们将 "x is a function returning a pointer to an array of pointers to functions returning char"
x 是 一个函数,它返回一个指针,该指针指向一个一维数组,该一维数组的元素为指针,这些指针分别指向多个函数,这些函数的返回值为 char 类型)
的描述用 x () * [] * () char 形式表示

程序 undcl 将把该形式转换为 char (*(*x())[])()

由于对输入的语法进行了简化,所以可以重用上面定义的 gettoken 函数
undcldcl 使用相同的外部变量

/* undcl: convert word descriptions to declarations */ 
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; 
}

目录、参考文献

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值