再谈C语言中的语句

news/2024/4/24 21:32:37/文章来源:https://blog.csdn.net/zenny_chen/article/details/130323974

在《C语言编程魔法书》中的14章第6节已经详细讨论了C语言的六种语句:标签语句(labeled statement)、复合语句(compound statement)、表达式语句(expression statement)、选择语句(selection statement)、迭代语句(iteration statement)以及跳转语句(jump statement)。这里,我将对其中的表达式语句中的一种特殊形式的语句再做讨论——即空语句null statement)。空语句在《C语言编程魔法书》中没怎么提到,这里正好做个补充。

空语句属于一种特殊的表达式语句,其表达式完全为空,即就一个分号( ; )所构成的一条表达式语句。这里我们需要注意,别把void表达式语句、空语句、空复合语句搞混了,以下先展示这三种不同的语句概念:

(void)0;    // 这是一条void表达式语句;           // 这是一条空语句{ }         // 这是一条空的复合语句

空语句可以出现在所有语句所能出现的地方,一般来说它没有特殊语义。但是这里各位需要注意的是,C语言中分号可作为一条表达式语句、声明和定义的结束符,也能表示一条空语句,这是分号的两种不同的语义。此外,C语言标准中其实把声明/定义同语句是分开的,因此声明和定义不属于语句。如果我们把“声明”写作为“声明语句”就显得不够专业了~😄 另外,复合语句的 } 可作为结束符;而在声明和定义中,只有定义函数时,} 才能作为结束符,而其他场合则不行。
下面我们举个例子:

int a = 0;    // 这里的分号表示声明的结束
a = 1; ;      // 这里有两个分号,左边的表示表达式语句的结束,右边的则表示一条空语句。因此这里有两条语句。

C语言中除了表达式语句和跳转语句,其他类型的语句都需要跟一条其他语句来构成一条完整语句的。比如说:

int a = 0;LABEL:    a = 1;        // 这是一条标签语句,在标签LABEL后面跟了一条表达式语句if(a > 0) a = -a;       // 这是一条选择语句,其后面伴随的是一条表达式语句作为一条完整的语句for(;;);                // 这是一条迭代语句,其后面伴随的是一条空语句构成一条完整的语句while(a > 0) { --a; }   // 这是一条迭代语句,其后面伴随一条复合语句构成一条完整的语句if(a < 0) a++;
else { --a; }            // 这里从if开始到最后 } 结束是一条完整的选择语句

以上这些概念请务必牢记,后面提到的容易触碰的“陷阱”其实就与语句的性质与条数有关。


我们上面已经提到了,标签语句、选择语句和迭代语句需要伴随一条这6种语句中的任何一条作为完整语句,而且无论是哪种情况均可跟任何一种语句。为了方便起见,我们可以把标签语句的LABEL: ,选择语句的if(condition)switch(selection),迭代语句的while(condition)for(sub-clauses)看作为“头”(header),然后后面再跟一条6种语句中的任何一种。因此,我们现在可以放开脑洞,看看以下代码,这些代码都是合法有效的,完全能通过编译的:

static void ctest(void)
{int a = 10;// 这里的选择语句头后面跟着一条标签语句if(a > 0)
LABEL:++a;// 这里的选择语句头后面跟着一条跳转语句if(a < 0)goto LABEL;// 这里的标签语句头后面跟着一条选择语句
LABEL2:if(a == 0);// 这里的选择语句头后面跟着一条空语句switch(a);// 这里的选择语句头后面跟着一条标签语句switch(a)
LABEL3:++a;    // 这里的 ++a; 将永远执行不到,除非显式使用goto跳转语句跳到LABEL3// 这里的选择语句头后面跟着一条标签语句switch (a)
LABEL4:{case 11:puts("case 11 reached!!");break;case 12:puts("case 12 reached!!");break;default:puts("default case reached!!");break;}if(a == 11){++a;goto LABEL4;}// 这里选择语句头后面跟着一条标签语句:case 12: --a;switch(a)case 12:--a;    // 这里的 --a; 将会被执行到。// 这里后面不允许添加 break; 跳转语句,因为到 --a; 为止已经是一条完整的选择语句了。
}

当你执行ctest函数之后,控制台将会打印出两行“ case 11 reached!! ”。看到这里,各位不要被C语言的灵活性吓到😅~正因如此,C语言在语法体系的设计方面是相当完备的。这里要详细讨论的是“LABEL4”这块。“LABEL4”所引出的标签语句与选择语句头switch(a)一起组成了一条完整的选择语句。这里与上面“LABEL3”处的选择语句不同,由于LABEL4标签语句是由一条带有case标签组的复合语句构成,因此这里完全可以跟switch配对上。因而,当执行到“LABEL4”上面的switch(a)之后,下面的case语句将会被选择执行。

此外,由于“LABEL4”所引出的是一条标签语句,因此这里变量a的上下文将会被保持住,后续对a的修改不影响这里的case判断。也就是说,在LABEL4处的a的值已经被hold住不变了,我们可以认为这里的a被赋值给了一个隐藏的临时常量,而后面的case标签语句将会根据此隐藏的临时常量的值进行选择执行。因此,这个函数被执行后将会两次都输出“ case 11 reached!! ”。

了解了上述语句的特性之后,我们以后在写选择语句、迭代语句的时候就要务必小心了,不要很悠哉地乱加分号,以免引起很难发现的bug。对于一般的表达式语句,后面跟多少分号都没问题,比如:

int a = 0;
a++;;;    // 这里其实有三条语句😄

但以下代码将是一个bug:

int a = 0;
for(int i = 0; i < 10; i++);    // 这里来了个分号……😅++a;

大家猜猜,上述代码执行后,a的值是几?😏


有了上述对C语言选择语句和迭代语句潜在的陷阱之后,我们下面就要聊聊一些更高级的主题了——当一个宏定义遇上选择语句或迭代语句之后,会发生神马化学反应~

我们有时为了封装一些代码块,会使用宏函数。不过在使用宏函数的时候也很有可能会引发一些意向不到的问题,尤其是该宏函数与选择语句、循环语句连用的场合,比如以下代码:

#define MY_MACRO(n)    if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); }

这句代码初步看起来没啥问题吧?然而,我们再看看以下代码会发生神马:

    int n = 100;if(n >= 10)MY_MACRO(10);else    // 发生编译错误:Expected expression{puts("n below ")}

我们看到,这个看似朴素的宏函数“MY_MACRO”上面加了if语句之后,这里就出问题了!问题原因在哪儿?上面已经详细讨论过了。由于MY_MACRO在预处理时进行宏展开后,实际上是一条完整的选择语句,然而这里MY_MACRO(10)后面又跟了一个分号,导致了该分号作为一条空语句的形式出现,从而与else无法配上。这里,MY_MACROz(10)宏展开后如以下代码所示:

    int n = 100;if(n >= 10)if((n) > 0){ printf("n = %d\n", n); puts("PASS!"); } ;else    // 发生编译错误:Expected expression{puts("n below 10!");}

所以,我们如果写成以下形式则可避免宏展开后发生编译错误:

    int n = 100;if(n >= 10){MY_MACRO(10);}else    // 发生编译错误:Expected expression{puts("n below 10!");}

当然,如果我们强迫第三方开发者一定要在选择、迭代语句后要用复合语句,那么似乎也属于要求比较苛刻了,那么我们自己定义的宏函数用哪些技巧可以使得调用起来更加灵活,不加如此约束呢?我们在市面上看到比较常用的是使用do-while迭代语句。do-while有何好处呢?while()在最后并不是以 } 符号结尾的,因此后面所跟的分号将作为整条迭代语句的结束符使用,而不会变成空语句。比如:

#define MY_MACRO(n)    do { \if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); }    \} while(0)    // 注意,这里可不能加分号int main(int argc, const char * argv[])
{int x = 100;if(x >= 10)MY_MACRO(10);else{puts("x below 10!");}
}

这么一来,我们可以看到上述代码编译、运行都不会有任何问题。当然,除了do-while之外还可以用其他技巧,总之,我们的目标就是要把宏函数调用后面的分号作为语句的结束符,而不是空语句。比如,我这里“别出心裁”地使用if-else选择语句,当然不能说这种解决方案完全完美,但还是能解决问题😊

#define MY_MACRO(n)     if((n) > 0) { printf("n = %d\n", n); puts("PASS!"); } \elseint main(int argc, const char * argv[])
{int x = 100;if(x >= 10)MY_MACRO(10);else{puts("x below 10!");}
}

这段代码编译运行也不会有任何问题。而且宏定义部分也比之前的do-while版本更简洁清爽。

我这里也属于抛砖引玉,大家可以随之打开脑洞,设计出更有创造力的表达方式。

进阶参考:深入探讨C语言中的声明与初始化

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

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

相关文章

webhub123 前端技术社区和技术交流学习网站导航

整理了学习前端技术可以参考学习和技术交流的一些网站集合&#xff0c;全部收录到 webhub123 前端技术社区和技术交流学习网站导航http://​www.webhub123.com/#/home/detail?projectHashid30929575&ownerUserid22053727 整理后的效果如下&#xff0c;我们已经按照不同类…

【Springboot系列】Springboot整合Swagger3不简单

1、缘由 Swagger是一个根据代码注解生成接口文档的工具&#xff0c;减少和前端之间的沟通&#xff0c;前端同学看着文档就可以开发了&#xff0c;提升了效率&#xff0c;之前很少写swagger&#xff0c;这次自己动手写&#xff0c;还是有点麻烦&#xff0c;不怎么懂&#xff0c;…

vue3中其他的变化

1.全局API的转移 Vue 2.x 有许多全局 API 和配置。 - 例如&#xff1a;注册全局组件、注册全局指令等。 //注册全局组件 Vue.component(MyButton, {data: () > ({count: 0}),template: <button click"count">Clicked {{ count }} times.</button> …

[读书笔记] 从问题和公式角度理解 Diffusion Model

[小全读书笔记] 从问题和公式角度理解 Diffusion Model 1. Diffusion Model的结构1.1 定义与限制1.2 定义与限制的数学体现 2. Diffusion Model的模型训练2.1 似然函数转换成ELBO2.2 拆解ELBO2.3 求解关键&#xff1a; q ( x t − 1 ∣ x t , x 0 ) q(x_{t-1}|x_t,x_0) q(xt−1…

CompletableFuture的基本使用和原理

CompletableFuture CompletableFuture是对Future的扩展和增强。CompletableFuture实现了Future接口&#xff0c;并在此基础上进行了丰富的扩展&#xff0c;完美弥补了Future的局限性&#xff0c;同时CompletableFuture实现了对任务编排的能力。借助这项能力&#xff0c;可以轻…

web事件循环

事件循环的应用&#xff1a;计时器 promise ajax node 单线程是异步产生的原因&#xff0c;事件循环时异步的实现方式 1.浏览器进程模型 进程&#xff1a;程序运行需要自己专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程。 每个应用至少又一个进程&#xff…

模板方法设计模式解读

目录 豆浆制作问题 模板方法模式基本介绍 基本介绍 模板方法模式的原理类图 模板方法模式解决豆浆制作问题 应用实例要求 思路分析和图解(类图) 模板方法模式的钩子方法 模板方法模式的注意事项和细节 豆浆制作问题 编写制作豆浆的程序&#xff0c;说明如下: 1) 制作豆…

【LeetCode】剑指 Offer 67. 把字符串转换成整数 p318 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/ 1. 题目介绍&#xff08;67. 把字符串转换成整数&#xff09; 写一个函数 StrToInt&#xff0c;实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。 …

研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案

研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案 目录 研究生考试 之 计算机网络第七版(谢希仁) 第一章 课后答案 一、简单介绍 二、计算机网络第七版(谢希仁) 第一章 课后答案 1、 计算机网络向用户可以提供哪些服务&#xff1f; 2、 试简述分组交换的要点。 3…

Kali下部署-Nessus漏扫工具

Nessus 是全世界最多人使用的系统漏洞扫描与分析软件。总共有超过75,000个机构使用Nessus 作为扫描该机构电脑系统的软件。 特点&#xff1a; 1、提供完整的电脑漏洞扫描服务&#xff0c;并随时更新漏洞库。 2、可以在本机或者是远端上进行遥控&#xff0c;进行系统的漏洞扫…

常见的四种排名函数的用法(sql)

四个排名函数&#xff1a; 1.row_number 2.rank 3.dense_rank 4.ntile 1. ROW_NUMBER&#xff08;排名场景推荐&#xff09; 1.1 介绍 在 SQL 中&#xff0c;ROW_NUMBER() 是一个窗口函数&#xff0c;它为结果集中的每一行分配一个唯一的序号。该函数的语法如下&#xff1a; …

JavaSE-part1

文章目录 Day01 面向对象特性1.java继承注意点2.多态2.1多态概述2.2多态中成员的特点:star::star:2.3多态的转型:star::star: 3.Super4.方法重写:star::star:5.Object类:star::star: Day02 面向对象特性1.代码块:star:(主要是初始化变量&#xff0c;先于构造器)2.单例设计模式:…

【移动端网页布局】移动端网页布局基础概念 ⑦ ( 在 PhotoShop 中使用 Cutterman 切二倍图 | 使用二倍图作为背景图像 )

文章目录 一、在 PhotoShop 中使用 Cutterman 切二倍图二、使用二倍图作为背景图像 一、在 PhotoShop 中使用 Cutterman 切二倍图 参考 【CSS】PhotoShop 切图 ③ ( PhotoShop 切图插件 - Cutterman | 下载、安装、启动、注册、登录 Cutterman - 切图神奇 插件 | 使用插件进行切…

3自由度并联绘图机器人实现写字功能(一)

1. 功能说明 本文示例将实现R305样机3自由度并联绘图机器人写字的功能。 2. 电子硬件 在这个示例中&#xff0c;采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&#xff08;兼容Arduino Uno&#xff09; 扩展板Bigfish2.1扩展板电池7.4V锂电池 3. 功能…

远程访问及控制ssh

SSH远程管理 OpenSSH服务器 SSH(Secure Shell) 协议 是一种安全通道协议。主要用来实现字符界面的远程登录、远程复制等功能。对通信数据进行了加密处理&#xff0c;用于远程管理其中包括用户登录时输入的用户口令。因此SSH协议具有很好的安全性------------&#xff08;同样…

d2l Transformer

终于到变形金刚了&#xff0c;他的主要特征在于多头自注意力的使用&#xff0c;以及摒弃了rnn的操作。 目录 1.原理 2.多头注意力 3.逐位前馈网络FFN 4.层归一化 5.残差连接 6.Encoder 7.Decoder 8.训练 9.预测 1.原理 主要贡献&#xff1a;1.纯使用attention的Enco…

Android程序员向音视频进阶,有前景吗

随着移动互联网的普及和发展&#xff0c;Android开发成为了很多人的就业选择&#xff0c;希望在这个行业能获得自己的一席之地。然而&#xff0c;随着时间的推移&#xff0c;越来越多的人进入到了Android开发行业&#xff0c;就导致目前Android开发的工作越来越难找&#xff0c…

EFI Driver Model(下)-USB 驱动设计

1、USB简介 通用串行总线&#xff08;英语&#xff1a;Universal Serial Bus&#xff0c;缩写&#xff1a;USB&#xff09;是一种串口总线标准&#xff0c;也是一种输入输出接口的技术规范&#xff0c;被广泛地应用于个人电脑和移动设备等信息通讯产品&#xff0c;并扩展至摄影…

我看谁没看过

vue在新窗口打开页面方法 const { href } this.$router.resolve({path: "/officePlatform/addPrompt"});window.open(href, "_blank"); 添加圆形标志 h3::before {content: "";display: inline-block;width: 13px;height: 13px;background: va…

NFT介绍及监管规则

什么是NFT NFT是Non-Fungible Token&#xff08;非同质化代币&#xff09;的缩写。 NFT是“Non-Fungible Token”的缩写&#xff0c;即非同质化代币。不同于FT&#xff08;Fungible Token&#xff0c;同质化代币&#xff09;&#xff0c;每一个NFT都是独一无二且不可相互替代的…