C语言KR圣经笔记 5.12 复杂声明

2024-01-28 20:12

本文主要是介绍C语言KR圣经笔记 5.12 复杂声明,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

5.12 复杂声明

C 语言有时会因为声明的语法而受到谴责,特别是涉及函数指针的声明语法。语法试图使声明和使用一致;在简单的情况下它的效果不错,但在更复杂的情况下会让人困惑,因为声明不能从左往右读,而且括号被过度使用了。如下两个声明

int *f();    /* f:返回int指针的函数 */

int (*pf)();    /* pf:指向返回int的函数的指针 */

它们之间的差异就说明了这个问题:* 是前缀操作符且优先级比括号低,为了强制得到正确的关联就需要括号。

尽管真正复杂的声明在实际工作中很少出现,但明白如何理解复杂声明,以及在有需要时知道如何创建复杂声明,都是很重要的。用 typdef 在几个小步骤中合成声明是一种不错的方式,将会在 6.7 节讨论。而本节我们给出的替代方式是一对程序,它们分别把合法的 C 语言转换成文字描述,以及把文字描述反向转换成 C 语言。文字描述是从左往右读的。

第一个程序叫 dcl, 更复杂一些。它将 C 语言声明转换成【英文】文字描述,如下:

char **argv:

        argv: pointer to pointer to char (指向char指针的指针)

int (*daytab)[13]

        daytab: pointer to array[13] of int (指向有13个元素的整数数组的指针)

int *daytab[13]

        daytab: arrar[13] of pointer to int (由13个整数指针组成的数组)

void *comp()

        comp: function returning pointer to void (返回 void * 指针的函数)

void (*comp)()

        comp: pointer to function returning void (指向返回 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 是一个 direct-dcl, 前面可能有 * 号。而 direct-dcl 可能是一个名称;或者是用圆括号括起来的 dcl;或是 direct-dcl 后面跟着一对圆括号;或是 direct-dcl 后面跟着一对方括号,其中的大小是可选的。

这个语法可以用来解析声明。例如下面这个声明

(*pfa[])()

pfa 被识别为一个名称,因此是 direct-dcl。然后 pfa[ ] 也是一个 direct-dcl。然后 *pfa[] 被识别为dcl,因此 (*pfa[]) 是 direct-dcl。然后 (*pfa[]) ( ) 是一个 direct-dcl,因此也是 dcl。也可以用如下的解析树来表示这个解析(其中 direct-dcl 简写为 dir-dcl):

dcl 程序的核心是一对根据这个语法来解析声明的函数,dcl 和 dirdcl。由于语法是递归定义的,当这两个函数识别出声明中的各个部分时,它们互相递归调用;程序被称为递归下降解析器。

/* dcl: 解析声明符 */
void dcl(void)
{int ns;for (ns = 0; gettoken() == '*';)    /* 计算*号个数 */ns++;dirdcl();while (ns-- > 0)strcat(out, " pointer to");
}/* dirdcl: 解析直接声明符 */
void dirdcl(void)
{int type;if (tokentype == '(') {    /* ( dcl ) */dcl();if (toketype != ')')printf("error: missing )\n");} else if (tokentype == NAME)    /* 变量名称 */strcpy(name, token);elseprintf("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。散乱多余的空格会让它解析混乱。它也没做太多的错误恢复,因此非法的声明也会使其混乱。这些改进作为本节后面的练习。

下面是全局变量和主例程。

#include <stdio.h>
#include <string.h>
#include <ctype.h>#define MAXTOKEN 100enum { NAME, PARENS, BRACKETS };void dcl(void);
void dirdcl(void);int gettoken(void);
int tokentype;           /* 最后一个token的类型 */
char token[MAXTOKEN];    /* 最后一个token的字符串 */
char name[MAXTOKEN];     /* 标识符名称 */
char datatype[MAXTOKEN]; /* 数据类型为 char, int 等 */
char out[1000];          /* 输出字符串 */main()    /* 将声明转换为文字 */
{while (gettoken() != EOF) {    /* 行中的第一个token是数据类型 */strcpy(datatype, token);out[0] = '\0';dcl();            /* 解析行的剩余部分 */if (tokentype != '\n')printf("syntax error\n");printf("%s: %s %s\n", name, out, datatype);}return 0;
}

gettoken 函数跳过空白和制表符,然后找到输入中的下一个token;“token”是一个名称,或是一对圆括号,或是一对可能包含数字的中括号,以及任意单个字符。

int gettoken(void)    /* 返回下一个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 = PARAENS;} 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;} elsereturn tokentype = c;
}

函数 getch 和 ungetch 在第四章中描述过。

反方向的处理会更简单,特别是如果我们不在乎生成了多余括号时。如 “x is a function returning an array of pointer to functions returning char” 这样的文字描述,在输入中表示为

x () * [] * () char

会被程序 undcl 转换为

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);} elseprintf("invalid input at %s\n", token);printf("%s\n", out);}return 0;
}

练习5-18、使 dcl 从输入错误中恢复。

练习5-19、修改 undcl 使其不产生多余的括号。

练习5-20、扩展 dcl ,使其能处理的声明可以包含函数参数类型,包含如 const 之类修饰符等。

(第五章完)

这篇关于C语言KR圣经笔记 5.12 复杂声明的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/654674

相关文章

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)

《MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)》掌握多表联查(INNERJOIN,LEFTJOIN,RIGHTJOIN,FULLJOIN)和子查询(标量、列、行、表子查询、相关/非相关、... 目录第一部分:多表联查 (JOIN Operations)1. 连接的类型 (JOIN Types)

Python程序的文件头部声明小结

《Python程序的文件头部声明小结》在Python文件的顶部声明编码通常是必须的,尤其是在处理非ASCII字符时,下面就来介绍一下两种头部文件声明,具有一定的参考价值,感兴趣的可以了解一下... 目录一、# coding=utf-8二、#!/usr/bin/env python三、运行Python程序四、

Go语言中使用JWT进行身份验证的几种方式

《Go语言中使用JWT进行身份验证的几种方式》本文主要介绍了Go语言中使用JWT进行身份验证的几种方式,包括dgrijalva/jwt-go、golang-jwt/jwt、lestrrat-go/jw... 目录简介1. github.com/dgrijalva/jwt-go安装:使用示例:解释:2. gi

Go 语言中的 Struct Tag 的用法详解

《Go语言中的StructTag的用法详解》在Go语言中,结构体字段标签(StructTag)是一种用于给字段添加元信息(metadata)的机制,常用于序列化(如JSON、XML)、ORM映... 目录一、结构体标签的基本语法二、json:"token"的具体含义三、常见的标签格式变体四、使用示例五、使用

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

如何合理管控Java语言的异常

《如何合理管控Java语言的异常》:本文主要介绍如何合理管控Java语言的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、Thorwable类3、Error4、Exception类4.1、检查异常4.2、运行时异常5、处理方式5.1. 捕获异常