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);
    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。散乱多余的空格会让它解析混乱。它也没做太多的错误恢复,因此非法的声明也会使其混乱。这些改进作为本节后面的练习。

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

#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;           /* 最后一个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;
    } else
        return 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);
            } else
                printf("invalid input at %s\n", token);
        printf("%s\n", out);
    }
    return 0;
}

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

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

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

(第五章完)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ快速上手

首先他的需求实在什么地方。我美哟明显的感受到。 它给我的最大感受就是脱裤子放屁——多此一举&#xff0c;的感觉。 他将信息发送给服务端中间件。在由MQ服务器发送消息。 服务器会监听消息。 但是它不仅仅局限于削峰填谷和稳定发送信息的功能&#xff0c;它还有其他重要…

Nacos(先解释专属名词,然后大白话讲解+安装配置教程+代码实例 信我,看了必会!!点进来吧各位大佬们)

Nacos 先来一个注意&#xff1a;以下涉及到配置和项目的创建&#xff0c;我的jdk是jdk17&#xff0c;idea是2023.2.4&#xff0c;并且是社区版的idea 1.什么是Nacos Nacos是Dyamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态…

重生奇迹MU平民玩家推荐的职业

女魔法师 女魔法师是一个非常适合平民玩家的职业选择。她拥有着强大的魔法攻击能力&#xff0c;可以轻松地击败敌人。而且女魔法师的装备价格相对较低&#xff0c;适合玩家们的经济实力。 精灵射手 精灵射手是一个非常灵活的职业选择。他们可以远程攻击&#xff0c;可以在战…

数据结构之生成树及最小生成树

数据结构之生成树及最小生成树 1、生成树概念2、最小生成树 数据结构是程序设计的重要基础&#xff0c;它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发&#xff0c;分析和研究计算机加工的数据的特性&#xff0c;以便为应用所…

centos7 挂载windows共享文件夹报错提示写保护

centos7挂载windows共享时&#xff0c;提示被共享的位置写保护&#xff0c;只能以只读方式挂载&#xff0c;紧接着就是以只读方式挂载失败 原因是组件少装了 yum install cifs-utils 安装完后&#xff0c;正常挂载使用。 下载离线安装包 下载离线包下载工具 下载离线安装包…

神经网络:表述(Neural Networks: Representation)

1.非线性假设 无论是线性回归还是逻辑回归&#xff0c;当特征太多时&#xff0c;计算的负荷会非常大。 案例&#xff1a; 假设我们有非常多的特征&#xff0c;例如大于 100 个变量&#xff0c;我们希望用这 100 个特征来构建一个非线性的多项式模型&#xff0c;结果将是数量非…

在windows环境下安装hadoop

Hadoop是一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。但这个架构是基于java语言开发的&#xff0c;所以要先进行jdk的安装&#xff0c;如果电脑已经配置过jdk或者是曾经运行成功过java文件&#xff0c;那就可以跳过第一步。 …

可解释性人工智能(XAI)概述

文章目录 每日一句正能量前言可解释性人工智能&#xff08;XAI&#xff09;定义研究的作用应用领域XAI的目标后记 每日一句正能量 一个人若想拥有聪明才智&#xff0c;便需要不断地学习积累。 前言 人工智能&#xff08;AI&#xff09;的发展速度迅猛&#xff0c;并在许多领域…

消息中间件之八股面试回答篇:三、RabbitMQ如何解决消息堆积问题(100万条消息堆积)+RabbitMQ高可用性和强一致性机制+回答模板

RabbitMQ中的消息堆积问题 当生产者发送消息的速度超过了消费者处理消息的速度&#xff0c;就会导致队列中的消息堆积&#xff0c;直到队列存储消息达到上限。之后发送的消息就会成为死信&#xff0c;可能会被丢弃&#xff0c;这就是消息堆积问题。 解决消息堆积有三种种思路…

【学网攻】 第(13)节 -- 动态路由(OSPF)

系列文章目录 目录 系列文章目录 文章目录 前言 一、动态路由是什么&#xff1f; 二、实验 1.引入 实验拓扑图 实验配置 实验验证 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学…

什么是数据库的三级模式两级映象?

三级模式两级映象结构图 概念 三级模式 内模式&#xff1a;也称为存储模式&#xff0c;是数据物理结构和存储方式的描述&#xff0c;是数据在数据库内部的表示方式。定义所有的内部记录类型、索引和文件组织方式&#xff0c;以及数据控制方面的细节。模式&#xff1a;又称概念…

Ubuntu20.04安装Google浏览器

一.在 Ubuntu 上安装 Google Chrome Chrome 不是一个开源的浏览器&#xff0c;并且它不被包含在标准的 Ubuntu 软件源中。在 Ubuntu 中安装 Google Chrome 是一个非常直接的过程。我们将会从官方网站下载安装文件&#xff0c;并且通过命令行工具来安装它。 1.1 下载 Google Ch…

Cesium材质特效

文章目录 0.引言1.视频材质2.分辨率尺度3.云4.雾5.动态水面6.雷达扫描7.流动线8.电子围栏9.粒子烟花10.粒子火焰11.粒子天气 0.引言 现有的gis开发方向较流行的是webgis开发&#xff0c;其中Cesium是一款开源的WebGIS库&#xff0c;主要用于实时地球和空间数据的可视化和分析。…

函数式接口当参数使用

如果函数式接口作为一个方法的参数&#xff0c;就以为着要方法调用方自己实现业务逻辑&#xff0c;常见的使用场景是一个业务整体逻辑是不相上下的&#xff0c;但是在某一个步骤有不同的逻辑&#xff0c;例如数据处理有不同的策略。上代码 package com.dj.lambda;import java.…

加密域可逆信息隐藏算法分类及评价指标

一、加密域可逆信息隐藏算法框架分类 加密图像可逆信息隐藏(RDHEI)是将图像加密和信息隐藏结合使用的一种技术。图像拥有者先对原始图像使用加密密钥keyc进行加密&#xff0c;信息隐藏者根据隐藏密钥keyd将秘密信息嵌入到密文图像中去。在接收端&#xff0c;接收者根据密钥key…

【Docker】快速入门手册

目录 1.概述 1.1.安装 1.2.阿里云镜像加速 1.3.运行原理 2.常用操作 2.1.帮助命令 2.2.镜像操作 2.3.容器操作 2.3.1创建、启动 2.3.2.退出、停止 2.3.3.进入交互式界面 2.3.4.守护式容器交互 2.3.5.查看 2.3.6.删除 2.3.7.拷贝 3.容器数据卷 3.1.概述 3.2.使…

linux03 用户权限

01.三种权限 02.UGO&#xff08;root账号&#xff09; 查看权限 不在root文件中写&#xff0c;是因为其他用户不能进来 举个例子 ll是ls -l 第一部分&#xff1a;权限&#xff08;11个字节&#xff09; 第一个&#xff1a;d/- d表示文件夹 - 表示一般文件 二到四&#xff1a…

R语言学习case6:ggplot基础画图(Scatter散点图)

step1: 导入ggplot2库文件 library(ggplot2)step2&#xff1a;带入自带的iris数据集 iris <- datasets::irisstep3&#xff1a;查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4&#xff1a;利用ggplot工具包绘图 plot1 <- ggplot(iris…

人工智能的圣杯:关于可解释AI(XAI)的一切

​​​​​​​ 在过去十年间&#xff0c;无数个人工智能解决方案在各大企业得到部署。 智能受众评测系统、智能财务合规系统、智能人员招聘系统&#xff0c;不一而足。 这期间&#xff0c;在企业客户却也始终存在一种怀疑态度&#xff1a;AI系统做出的产品部署是否真的值得…

初识人工智能,一文读懂机器学习之逻辑回归知识文集(6)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…