2.3 注意作为语句结束标志的分号

​ 在c程序中如果不小心多写了一个分号可能不会造成什么不良后果:这个分号也许会被视作一个不会产生任何实际效果的空语句;或者编译器会因为这个多余的分号而产生一条警告信息,根据警告信息的提示能够很容易去掉这个分号。一个重要的例外情形是在if或者while语句之后需要紧跟一条语句时,如果此时多了一个分号,那么原来紧跟在if或者while子句之后的语句就是一条单独的语句,与条件判断部分没有了任何关系。考虑下面的这个例子;

if (x[i] > big);
big = x[i];

编译器会正常地接受第一行代码中的分号而不会提示任何警告信息,因此编译器对这段程序代码的处理与对下面这段代码的处理就大不相同:

if (x[i] > big)
big = x[i];

前面第一个例子(即在if后多加了一个分号的例子)实际上相当于

if (x[i] > big) { }
big = x[i];

当然,也就等同于(除非x、I或者big是有副作用的宏)

big = X[i];

如果不是多写了一个分号,而是遗漏了一个分号,同样会招致麻烦。例如:

if (n<3)return
logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];

此处的return语句后面遗漏了一个分号;然而这段程序代码仍然会顺利通过

编译而不会报错,只是将语句

logrec.date = x[0];

当作了return语句的操作数。上面这段程序代码实际上相当于:

if (n<3)return logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];

​ 如果这段代码所在的函数声明其返回值为void记,编译器会因为实际返回值的类型与声明返回值的类型不一致而报错。然而,如果一个函数不需要返回值(即返回值为void),我们经常在函数声明时省略了返回值类型,但是此时对编译器而言会隐含地将函数返回值类型视作int类型。如果是这样,上面的错误就不会被编译器检测到。在上面的例子中,当n>=3时,第一个赋值语句会被直接跳过, 由此造成的错误可能会是一个潜伏很深、极难发现的程序Bug。

​ 还有一种情形,也是有分号与没分号的实际效果相差极为不同。那就是当一 个声明的结尾紧跟一个函数定义时,如果声明结尾的分号被省略,编译器可能会把声明的类型视作函数的返回值类型。考虑下面的例子:

struct logrec{int date;int time;int code;
}
main()
{…
}

在第一个}与紧随其后的函数main定义之间,遗漏了一个分号。因此,上面代码段实际的效果是声明函数main的返回值是结构logrec类型。写成下面这样,会看得更清楚:

struct logrec{int date;int time;int code;
}main()
{…
}

如果分号没有被省略,函数main的返回值类型会缺省定义为int类型。

​ 在函数main中,如果本应返回一个int类型数值,却声明返回一个struct logrec 类型的结构,会产生怎样的效果呢?我们把它留作为本章结尾的一个练习。虽然 刻意地往消极面去联想也许有些“病态”,但对要考虑到各种意外情形的程序设计 (比如航空航天或医疗仪器的控制程序),却是不无裨益的。

2.4 switch 语句

C语言的switch语句的控制流程能够依次通过并执行各个case部分,这一点 是C语言与众不同之处。考虑下面的例子,两段程序代码分别用C语言和Pascal 语言编写:

switch(color){case 1: printf( “red");break;
case 2: printf(“yellow");break;
case 3: printf(“blue”);break;
}
case color of
1:      write(‘red’);
2:      write(‘yellow’);
3:      write('blue');
end

两段程序代码要完成的是同样的任务:根据变量color的值(1, 2或3),分 别打印出red, yellow或blue。两段程序代码非常相似,只有一个例外情形:那就是用Pascal语言编写的程序段中每个case部分并没有与C语言的break语句对应的部分。之所以会这样,原因在于C语言中把case标号当作真正意义上的标号,因此程序的控制流程会径直通过case标号,而不会受到任何影响。而另一方面,在Pascal语言中每个case标号都隐含地结束了前一个case部分。

让我们从另一个角度来看待这个问题,假设将前面用C语言编写的程序代码 段稍作改动,使其在形式上与用Pascal语言编写的代码段类似:

switch (color)
{case 1:printf(“red");
case 2:printf(“yellow”);
case 3:printf(“blue”);
}

又进一步假定变量color的值为2。最后,程序将会打印出

yellowblue

因为程序的控制流程在执行了第二个printf函数的调用之后,会自然而然地顺序执行下去,第三个printf函数调用也会被执行。

​ C语言中switch语句的这种特性,既是它的优势所在,也是它的一大弱点。 说它是一大弱点,是因为程序员很容易就会遗漏各个case部分的break语句,造成一些难以理解的程序行为。说它是优势所在,是因为如果程序员有意略去一个 break语句,则可以表达出一些釆用其他方式很难方便地加以实现的程序控制结构。特别是对于一些大的switch语句,我们常常会发现各个分支的处理大同小异: 对某个分支情况的处理只要稍作改动,剩余部分就完全等同于另一个分支情况下的处理。

​ 例如,考虑这样一个程序,它是某种假想的计算机的解释器(相当于虚拟机)。 这个程序中包含有一个switch语句,用来处理每个不同的操作码。在这种假想的计算机上,只要将第二个操作数的正负号反号后,减法运算和加法运算的处理本质上就是一样的。因此,如果我们可以像下面这样写代码,无疑会大大方便程序的处理:

case SUBTRACT:opnd2 = -opnd2;/*此处没有break语句*/
case ADD:

​ 当然,像上面的例子那样添加适当的程序注释是一个不错的做法。当其他人 阅读到这段代码时,就能够了解到此处是有意省去了一个break语句。

​ 再看另一个例子,考虑这样一段代码,它的作用是一个编译器在查找符号时跳过程序中的空白字符。这里,空格键、制表符和换行符的处理都是相同的,除 了当遇到换行符时程序的代码行计数器需要进行递增:

case '\n':linecount++;/*此处没有break语句*/
case '\t':
case ' ':

2.5 函数调用

​ 与其他程序设计语言不同,C语言要求:在函数调用时即使函数不带参数, 也应该包括参数列表。因此,如果f是一个函数,

f();

是一个函数调用语句,而

f;

却是一个什么也不做的语句。更精确地说,这个语句计算函数f的地址,却并不调用该函数。

1.6 “悬挂” else引发的问题

这个问题虽然已经为人熟知,而且也并非C语言所独有,但即使是有多年经 验的C程序员也常常在此失误过。

考虑下面的程序片段:

if (x == 0)if (y==0) error();
else
{z = x + y;f(&z);
}

这段代码中编程者的本意是应该有两种主要情况,x等于0以及x不等于0。 对于x等于0的情形,除非y也等于0 (此时调用函数error ),否则程序不作任何处理;对于x不等于0的情形,程序首先将x与y之和赋值给z,然后以z的地址为参数来调用函数f。

然而,这段代码实际上所做的却与编程者的意图相去甚远。原因在于C语言中有这样的规则,else始终与同一对括号内最近的未匹配的if结合。如果我们按照上面这段程序实际上被执行的逻辑来调整代码缩进,大致是这个样子:

if (x == 0)
{if (y == 0)error();else {z = x + y;f(&z);}
}

也就是说,如果x不等于0,程序将不会做任何处理。如果要得到原来的例 子中由代码缩进体现的编程者本意的结果,应该这样写:

if (x == 0)
{if (y == 0)error();
}
else
{z = x + y;f(&z);
}

现在,else与第一个if结合,即使它离第二个if更近也是如此,因为此时第 二个if已经被括号“封装”起来了。

有的程序设计语言在if语句中使用收尾定界符来显式地说明”例如,在Algol68语言中,前面提到的例子可以这样写:

if x = 0
then  if y = 0then errorfi
else  z :=  x + v;
f(z)
fi

像上面这样强制使用收尾定界符完全避免了 “悬挂” else的问题,付出的代

价则是程序稍稍变长了一点。有些C程序员通过使用宏定义也能达到类似的效果:

#define IF {iF(
\#define THEN)}
\#define ELSE } else {\#define FI }}

这样,上例中的C程序就可以写成:

IF  x==0
THEN IF y==0THEN error();FI
ELSE z=x+y;
f(&z);
FI

如果一个C程序员过去不是长期浸淫于Algol 68语言,他会发现上面这段代 码难于卒读。这样一种解决方案所带来的问题可能比它所解决的问题还要更糟糕。

C陷阱与缺陷-疑难问题理解04相关推荐

  1. C陷阱与缺陷-疑难问题理解06

    3.4 避免"举隅 [yú] 法" "举隅法"(synecdoche)是一种文学修辞上的手段,有点类似于以微笑表示喜悦.赞许之情,或以隐喻表示指代物与被指物的相 ...

  2. 《C陷阱与缺陷》一导读

    前 言 C陷阱与缺陷 对于经验丰富的行家而言,得心应手的工具在初学时的困难程度往往要超过那些容易上手的工具.刚刚接触飞机驾驶的学员,初航时总是谨小慎微,只敢沿着海岸线来回飞行,等他们稍有经验就会明白这 ...

  3. 《Java解惑》陷阱和缺陷的目录

    陷阱和缺陷的目录 一.词汇问题 1.字母l在许多字体中都与数字1相像. 2.负的十六进制字面常量看起来像是正的. 3.八进制字面常量与十进制字面常量相像. 4.ASCII字符的Unicode转义字符容 ...

  4. C语言三剑客之《C陷阱与缺陷》一书精华提炼

    点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! 1.C陷阱与缺陷概述 C语言像一把雕刻刀,锋利,并且在技师手中非常有用.和任何锋利的 ...

  5. c语言局限性,C语言陷阱与缺陷.pdf

    C 语言陷阱和缺陷[1] winxos 11-01-28 winxos 11-01-28 原著:Andrew Koenig - AT&T Bell Laboratories Murray Hi ...

  6. 写给大数据从业者:数据科学的5个陷阱与缺陷

    来源 | AI 前线 作者 | 陈炬,责编 | Carol 出品 | CSDN云计算(ID:CSDNcloud) 导读: 这篇分享主要总结了数据从业人员在实践中可能遇到的陷阱与缺陷.跟其他新起的行业一 ...

  7. 《C陷阱与缺陷》一第1章 词法“陷阱”1.1 =不同于==

    本节书摘来自异步社区<C陷阱与缺陷>一书中的第1章,第1.1节,作者 [美]Andrew Koenig,更多章节内容可以访问云栖社区"异步社区"公众号查看 第1章 词法 ...

  8. c语言 去掉双引号_技术分享|浅谈C语言陷阱和缺陷

    良好的软件架构.清晰的代码结构.掌握硬件.深入理解C语言是防错的要点,人的思维和经验积累对软件可靠性有很大影响.C语言诡异且有种种陷阱和缺陷,需要程序员多年历练才能达到较为完善的地步.软件的质量是由程 ...

  9. 《C陷阱与缺陷》学习笔记

    第一章 词法陷阱 笔记本:<C陷阱与缺陷> 创建时间:2018/4/23 22:06:21                                                  ...

最新文章

  1. Xdebug的安装-(无错可执行版)
  2. 0x31.数论 - 质数
  3. 牛客网华为机试题 字符串问题 记录
  4. leetcode算法题--Magical String
  5. 第十八章 20结构体与string
  6. 【简便解法】1084 Broken Keyboard (20 分)_16行代码AC
  7. SAP UI5 jQuery.sap.setObject
  8. mysql技术大会2020_2020年数据库技术大会助力技术提升
  9. 认识计算机网络试讲稿,操作系统简介试讲教案.pdf
  10. 95-38-050-Buffer-UnpooledHeapByteBuf
  11. MySQL高级-索引
  12. oracle和sql server中,取前10条数据语法的区别
  13. HDU2522 A simple problem【分数与小数】
  14. Python:matplotlib绘制条形图
  15. 转:三款免费好用的Gif录屏神器
  16. 数字信号处理声音降噪实验
  17. easyui datagrid deleteRow(删除行)的BUG!
  18. 天创速盈带你了解拼多多新店运营技巧
  19. 创建网页文件html,HTML快速入门之创建网页文件
  20. ubuntu electron-rebuild 我的成功方法

热门文章

  1. 软件测试习题————1.录制登录—订票—退出代码。2.实现舱位随机选择。3.设置检查点检查订单编号格式是否正确,将测试结果采用自定义的方式写入测试报告。
  2. velodyne 配置命令
  3. 您试图连接的 SQL Server 2016 实例未安装
  4. 花了6个月时间完成本科优秀毕业设计,我做了什么?
  5. 显示农历天气时钟小部件下载_iOS 14 Beta 2更新内容整理:图标调整、增加新的小部件及其他...
  6. 叮咚买菜基于Doris引擎的应用实践
  7. centos利用yum安装卸载软件常用命令
  8. 实现HTML5 裁剪图片并上传
  9. 【深度强化学习】8. DDPG算法及部分代码解析
  10. 【MOT】目标追踪DeepSORT与ByteTrack