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 之类修饰符等。

(第五章完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/654486.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux文本三剑客---sed经典案例

Sed介绍&#xff1a; sed是一种流编辑器&#xff0c;它一次处理一行内容。处理时&#xff0c;把当前处理的行存储在临时缓冲区中&#xff0c;称为“模式空间”&#xff0c;接着用sed命令处理缓冲区中的内容&#xff0c;处理完成后&#xff0c;把缓冲区的内容送往屏幕。接着处理…

Mybatis-plus原生pages分页未生效的解决方案

文章目录 前言原因1、Mybatis Plus版本的问题2、Mapper.xml文件中SQL语句格式问题3、Mybatis Plus默认分页拦截器问题4、分页参数传参问题5、分页配置的问题 解决方案1、升级对应的Mybatis-plus版本分页插件配置问题3、自定义分页拦截器4、正确的参数5、不同版本的配置文件3.4.…

Hugging Face创始人分享:企业如何在ChatGPT浪潮下实现战略布局

Hugging Face创始人兼首席执行官 Clem Delangue在IBM一年一度的 THINK大会中研讨了当前人工智能发展趋势&#xff0c;特别是ChatGPT模型以及其对行业的影响。他的演讲还涉及到一个关键的议题&#xff0c;在ChatGPT这样的通用模型出现后&#xff0c;企业如何在人工智能领域找到自…

QR 分解cpu程序

1. 代码 Makefile EXE : hello_qrSRC_QR qr_main.c qr_func.c $(EXE): $(SRC_QR)gcc $^ -o $ -lm.PHONY: clean clean:-rm -rf $(EXE) qr_main.c #include "stdio.h"int maqr(double* a,int m,int n,double* q);int main() {int i,j;static double q[4][4],a[4]…

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求。 Similarities:精准相似度计算与语义匹配搜索工具包,多维度实现多种算法,覆盖文本、图像等领域,支持文搜、图搜文、图搜图匹配搜索 Similar…

window下如何安装ffmpeg(跨平台多媒体处理工具)

ffmpeg是什么? FFmpeg是一个开源的跨平台多媒体处理工具&#xff0c;可以用于录制、转换和流媒体处理音视频。它包含了几个核心库和工具&#xff0c;可以在命令行下执行各种音视频处理操作&#xff0c;如剪辑、分割、合并、媒体格式转换、编解码、流媒体传输等。FFmpeg支持多…

C++类和对象引入以及类的介绍使用

文章目录 一、面向过程和面向对象的初步认识二、类的引入2.2 类的引入 三、类的访问限定符及封装3.3 访问限定符3.4 【面试题】C中struct和class的区别3.5 类的两种定义方式 四、封装【面试题】面向对象的三大特性 五、类的作用域六、类的实例化七、类对象模型7.1 类对象的存储…

postman之接口参数签名(js接口HMAC-SHA256签名)

文章目录 postman之接口参数签名&#xff08;js接口签名&#xff09;一、需求背景二、签名生成规则三、postman js接口签名步骤1. postman设置全局、或环境参数2. 配置Pre-request Scripts脚本 四、Pre-request Scripts脚本 常见工作整理1. js获取unix时间戳2. body json字符串…

day35WEB 攻防-通用漏洞XSS 跨站反射存储DOMBeef-XSS

目录 一&#xff0c;XSS 跨站-原理&分类&手法&探针 1、原理 2、分类 3、危害 二&#xff0c;反射型XSS 1&#xff0c;案例演示 三&#xff0c;存储型XSS 1&#xff0c;案例演示 四&#xff0c;DOM 型XSS 五&#xff0c;XSS 利用环境-XSS 平台&Beef-XS…

探讨UI自动化测试几步骤

随着软件开发的不断发展&#xff0c;UI自动化测试变得越来越重要&#xff0c;它能够提高测试效率、降低人为错误&#xff0c;并确保软件交付的质量。本文将介绍UI自动化测试的一般步骤和一些最佳实践&#xff0c;以帮助开发团队更好地实施自动化测试。 需求分析和选择测试工具&…

QT+VS实现Kmeans++

1、Kmeans的原理如下&#xff1a; &#xff08;1&#xff09;首先选取样本中任一数据点作为第一个聚类中心&#xff1b; &#xff08;2&#xff09;计算样本每一个数据点至现所有聚类中心的最近距离&#xff0c;并记录下来&#xff1b; &#xff08;3&#xff09;逐一挑选所…

电脑监控软件:提升工作效率与保障信息安全

随着信息技术的飞速发展&#xff0c;电脑已经成为了我们日常生活和工作中不可或缺的工具。然而&#xff0c;随着电脑使用频率的增加&#xff0c;工作效率低下、信息泄露等问题也日益凸显。为了解决这些问题&#xff0c;电脑监控软件应运而生&#xff0c;为我们的工作和生活提供…

基于vue实现待办清单案例

一、需求 新增内容&#xff1b; 删除内容&#xff1b; 统计操作&#xff1b; 清空数据。 示例图&#xff1a; 二、代码演示 1、基础准备 index.css代码 html, body {margin: 0;padding: 0; } body {background: #fff ; } button {margin: 0;padding: 0;border: 0;backgr…

马斯克将为 X 平台设立内容审核中心;Google Bard 冲上 LLM 排行榜第二

马斯克旗下社交媒体平台 X&#xff08;原 Twitter&#xff09;的业务运营主管 Joe Benarroch 表示&#xff0c;将在得州奥斯汀设立一个新的内容审核中心&#xff0c;招募 100 名全职员工。 该中心将重点打击平台上虐待儿童相关内容&#xff0c;并执行平台的仇恨言论限制政策&am…

雪深传感器的工作原理

TH-XL2雪深传感器的工作原理主要是利用超声波的发射和反射来测量雪的厚度。传感器发射超声波&#xff0c;超声波遇到雪表面后会反射回来&#xff0c;传感器再接收反射回来的超声波&#xff0c;通过计算超声波的传播时间和速度&#xff0c;就可以得出雪的厚度。 另外&#xff0c…

幻兽帕鲁服务器价格,这个价格不够电费的

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云换手帕服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G3…

STM32学习笔记(三) —— GPIO点亮LED

1.GPIO简介 GPIO&#xff0c;全称是General-purpose input/output&#xff08;通用输入输出&#xff09;。在单片机中是表示能被控制的引脚&#xff0c;能检测输入信号的高低电平&#xff0c;也能输出高低电平控制外部设备。STM32F103RCT6一共有64个引脚&#xff0c;其中有51个…

SQL注入-sqli-labs-master第一关

实验环境&#xff1a; Nginx.1.15.11 MySQL&#xff1a;5.7.26 实验步骤&#xff1a; 1.第一步&#xff1a; 在id1后加入一个闭合符号&#xff0c;如果报错&#xff0c;再在后面加上 -- 将后面注释掉&#xff0c;如果不报错&#xff0c;则证明为字符型。 http://127.0.0.1/…

(Sping Xml方式整合第三方框架)学习Spring的第十天

Spring整合mybatis 1 . 导入Mybatis整合Spring的相关坐标 <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.13.RELEASE</version></dependency><dependency><…

ps缺少msvcp140.dll要怎么办?多种解决msvcp140.dll的方法分享

当您在尝试打开Adobe Photoshop时&#xff0c;如果遭遇一个典型的错误&#xff1a;“程序无法启动&#xff0c;因缺少MSVCP140.dll文件”&#xff0c;请放心&#xff0c;这并不少见&#xff0c;许多Photoshop用户都可能曾面临过这种情况。处理这个问题实际上是相当简单的。接下…