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

相关文章

C语言中联合体union的使用

本文编辑整理自: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=179471 一、前言 “联合体”(union)与“结构体”(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量

Tolua使用笔记(上)

目录   1.准备工作 2.运行例子 01.HelloWorld:在C#中,创建和销毁Lua虚拟机 和 简单调用。 02.ScriptsFromFile:在C#中,对一个lua文件的执行调用 03.CallLuaFunction:在C#中,对lua函数的操作 04.AccessingLuaVariables:在C#中,对lua变量的操作 05.LuaCoroutine:在Lua中,

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

操作系统实训复习笔记(1)

目录 Linux vi/vim编辑器(简单) (1)vi/vim基本用法。 (2)vi/vim基础操作。 进程基础操作(简单) (1)fork()函数。 写文件系统函数(中等) ​编辑 (1)C语言读取文件。 (2)C语言写入文件。 1、write()函数。  读文件系统函数(简单) (1)read()函数。 作者本人的操作系统实训复习笔记 Linux

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

C语言 将“China”译成密码

将“China”译成密码,密码规律是:用原来的字母后面的第4个字母代替原来的字母。例如,字母“A”后面的第4个字母是“E”,用“E”代替“A”。因此,“China”应译为“Glmre”。编译程序用付赋初值的方法使c1,c2,c3,c4,c5这五个变量的值分别为“C”,“h”,“i”,“n”,“a”,经过运算,使c1,c2,c3,c4,c5分别变成“G”,“l”,“m”,“r”,“e”。分别用put

LVGL快速入门笔记

目录 一、基础知识 1. 基础对象(lv_obj) 2. 基础对象的大小(size) 3. 基础对象的位置(position) 3.1 直接设置方式 3.2 参照父对象对齐 3.3 获取位置 4. 基础对象的盒子模型(border-box) 5. 基础对象的样式(styles) 5.1 样式的状态和部分 5.1.1 对象可以处于以下状态States的组合: 5.1.2 对象

DDS信号的发生器(验证篇)——FPGA学习笔记8

前言:第一部分详细讲解DDS核心框图,还请读者深入阅读第一部分,以便理解DDS核心思想 三刷小梅哥视频总结! 小梅哥https://www.corecourse.com/lander 一、DDS简介         DDS(Direct Digital Synthesizer)即数字合成器,是一种新型的频率合成技术,具有低成本、低功耗、高分辨率、频率转换时间短、相位连续性好等优点,对数字信