任何标识符(包括变量、函数名等)都要先声明再使用。如果在程序中没有声明fahr的变量,那就会出现类似下面的错误。
这时,我们要加上类似下面这样的语句:
int fahr;
更多的时候,是因为我们定义了一个变量,但使用的时候拼写错了,比如定义了int fahr; 但使用的时候写成了farh。
注:关于声明(declaration)和定义(definition),两者还是有区别的,具体就不展开了。
(5)除法操作符/引发的错误
执行下面这段程序后,celsius并不是我们期望的值37.8度,而是celsius=0.000000,这是为什么呢?

原因就在于除法运算/。
在这里,涉及到C语言运算的一个重要规则:
任何双目操作符进行运算,两个操作数的类型必须一致。
在上面的表达式celsius=5/9*(fahr-32)中,第一步执行5/9,两个操作数都是整数。对于整数,/运算符就是做取整的除法,结果还是整数,因此是0。从而,右边表达式的值就是0。最后,赋值操作=也要求操作符两边的数据类型一致,此时c语言取整,会进行默认的数据类型转换,把右边的整数0先转换为小数后赋值给celsius。
要想改为正确的程序很简单,把5或9中的至少一个改为小数即可,比如5改为5.0后,运行结果就正确了。
类似于这样的错误,在下面这段计算1-1/3+1/5-1/7+…的代码里也同样存在,你能找出它吗?

(6)输入法错误
在源代码中切换到中文输入模式,输入了字符,常见的是逗号、分号、小括号和引号,比如下面代码第一行行末的分号和第二行最右侧的小括号。

此时,编译器会提示类似下面这样的错误,这里类似243这样的是用转义字符给出的非标准ASCII字符编码提示。
修改错误很简单,切换回英文输入模式即可。
(7)printf()的常见错误
printf中的f是指formatted,即格式化输出。这个函数里面有若干个参数,第一个参数用字符串给出了输出字符串的格式,里面有普通的字符,也有以%开头的格式化控制符,比如%d, %f, %c等,我喜欢把它们称为占位符。
原则上每出现一个%的占位符,后面就要用一个对应的参数来替换这个占位符,并且要求前后类型一致,否则运行时就会出现比较“离奇”的结果。
比如,下面这段代码:

运行结果是:
其原因在于celsius是float类型的,但在printf()中把它作为%d格式输出,也就是把celsius里以浮点格式存储的32位比特被解释成了整数予以输出,所以会出现奇怪的结果。
为了深入理解这一点,有必要就数据类型多说几句。
所有的数据在计算机里都以二进制的形式存储,但每个数据都有相应的数据类型。
数据类型决定下面几个方面:
(1)数据占用的存储空间的大小;
(2)数据的编码和存储格式;
(3)能对数据进行的操作。
浮点数有浮点数的编码和存储格式c语言取整,整数有整数的编码和存储格式,字符串有字符串的存储格式,输出格式必须与存储格式相匹配才行,否则就会出现错误的输出。
在上面的例子中,为了得出正确的结果,直接把celsius=%d改为celsius=%f即可,如下:
还有一个典型的错误是引号直接把整个字符串都包括进去了,如下:
这时,编译器并不会报错,但运行的时候会出现意料之外的结果,比如:
这个printf()函数明明少了两个参数,为什么还能运行出(虽然是不正确的)结果呢?
要解释这一点,需要理解堆栈和printf()函数的调用方式,有兴趣的可以自行搜索一下,结果一定会让你收获满满。
(8)scanf()的常见错误
与printf()类似,scanf()是格式化输入,函数的第一个参数和printf()的第一个参数类似,用于给出输入字符串的格式,后面的参数用于指定格式化输入的数据存储的位置。
在这个函数的使用过程中,会有不少常见的错误。
第一点和printf()一样,就是前面的%所表示的类型和后面存储数据的变量类型要一致,否则就会出现类似的数据类型解释出错。
第二个错误和printf()也一样,就是引号把整个字符串都包括进去。这种情况编译器也不会报错,但运行结果会出错。要解释这一点,同样需要理解堆栈和scanf()函数的调用方式,有兴趣的可以自行搜索一下。
第三个典型错误就是少写了取地址运算符&,比如:
这种情况编译也不会出错,但运行程序的话很多时候就直接弹窗报错。为了理解这一点,有必要了解一下&的含义。
我们用int fahr;定义变量的时候,要为这个变量在内存中分配一块存储空间。scanf()表示从键盘输入一个整数,然后放到fahr所在的位置去,因此scanf()需要我们告诉它fahr变量的门牌地址。取地址操作符&就是用于获取fahr的地址的。&操作得到的是一个地址,本质上也是一个整数,而fahr本身表示取fahr变量的内容,其值也是一个整数,因此编译不会报错。但显然,如果不用&,我们输入的数据会放错位置,并没有放到fahr变量的地方,而是会放到一个我们意想不到的地方。很多时候,这个位置是不允许写入的地方,从而会触发操作系统的保护机制,终止程序的运行。
第四种典型错误就是在scanf()的第一个字符串中写了普通字符,但输入时没写。
比如上面的代码,运行时比如输入“80,100″,就能正确输入,但如果输入”80 100″,那结果就不是预期的。
其原因在于scanf()的第一个字符串指定了输入的格式,里面如果有逗号,我们在输入的时候也要原封不动地输入。
反过来也会产生错误。如果程序里是scanf(“%d%d”, &lower, &upper);,但输入时多了个逗号,变成” 80, 100″,那输入数据就不对了。
所以,使用scanf()输入时,输入数据必须和格式化输入字符串严格匹配。
(9)==和=不分
这个是编程时常见的一个逻辑错误,而且很隐蔽。
比如下面这段代码,如果输入8 3, 输出会是什么?

如果认为输出为8/3=2,那就错了。
因为b=0中的=并非是关系运算符,而是赋值运算,也就是执行if(b=0)后,是把0赋值给b,并且这个赋值表达式 b=0 的值就是b的值,为0。因此,这个条件不成立,那会执行else里的打印语句。
可问题是,为什么不输出8/3=2呢?
这是因为此时b已经等于0了, a/b执行的是8/0,显然会出错。
修改办法当然很简单,就是把b=0改为b==0即可。
(10)for语句有关的错误
第一个错误是for后面直接加分号。
比如,运行下面的程序,会得出1+2+…+100=101的结果。

为什么呢?就因为在for后面加了分号。之前讲过,分号表示一条语句的结束。因此,sum=sum+i就是for语句执行结束之后再执行的一条语句。
上面这条语句的i++会执行100次,但循环体为空,也就是只更新循环变量,但什么实际工作都不做。等到它结束时,循环变量i的值为101,因此最后会输出101。
另一个典型的错误是for语句的括号里分号用逗号,类似于这样:
幸运的是,这个编译器会报错,如下:

所以,对于for语句来说,小括号里的两个分号不能少,小括号最后的分号要谨慎(除非就是想放循环体为空的语句)。
对for语句小括号里的三条语句而言,每一条都可以空着,但分号不能少。比如下面的,初始化i=1可以在循环体外面做,更新循环变量的i=i+1也可以在循环体里面做。

(11)使用没有初始化的变量
局部变量如果没有初始化,有些编译器会默认初始化为0,有些编译器则不会初始化,从而变量存储的是一个随机值,所以要万分小心。但很遗憾,很多时候我们注意不到,导致结果可能与我们预期的不一致。
比如如果运行下面的代码,很可能结果输出为0,而不是我们预期的10!=362880。其原因就在于我们没有对product初始化,如果编译器将product初始化为0,那么结果就一直是0。

修改很简单,在使用前将product初始化为1即可。
(12)越界
同样是上面的程序,如果我们计算的是30!而不是10!,那会怎样?

运行程序,会发现出现了负数的结果,这显然不对。
这个原因就在于product被定义为一个int类型,而这个数据类型是32位的,它的表示范围为-231~231-1,显然30!超出了这个范围。
这个问题我们只能部分予以解决,比如把product改为double类型,再运行这个程序就得到了下面的结果。当然,double类型也是有表示范围的,运算结果超出了其表示范围就会出错。
(13)不是错误的错误
使用集成编辑环境编译和运行程序时,还有一个经常碰到的错误提示,如下:
这个提示信息告诉我们,不能打开某个.exe的输出文件。出现这个错误是因为之前运行这个exe可执行程序还没有运行结束,窗口也没有关闭。因此,这个exe文件已经被打开了,而现在再次编译源程序的时候,试图去写这个exe文件,这个时候就会出现Permission denied的错误。因为操作系统不允许去写一个正在被打开的可执行程序。
直接关掉上一次未完成的运行窗口,再编译这个程序,就不会有错误了。
总结一下:
(1)计算机是很笨的,它没有半点弹性,只会严格按照规范来。
(2)为了能和计算机和谐相处并驾驭它,我们得适应计算机的脾气和逻辑,而不是让它来迎合我们。
(3)为了最大程度提升编程能力,每次出现错误后,不是急于去修改代码,而是先去理解为什么计算机会给出这个错误的结果。
(4)计算机一定是有理由的。
(5)英语还是要懂一点。
都看到这了,点个赞和在看,再动动手指分享给需要的朋友呗。
(全文完)
作者:昍爸,中科院计算机博士,大学教授,博士生导师,曾获初中和高中全国数学奥林匹克联赛一等奖,江苏赛区第一名,高考数学满分。著有畅销书《给孩子的数学思维课》与《给孩子的数学解题思维课》。
经过几年的打磨和实践,昍爸的思维课终于上线了,扫码即可试听或购买。更多课程介绍参见《》
公众号创立6年多,为大家整理了50篇必读文章收藏版,欢迎大家翻阅转发!不想看网页的可以直接文末扫码购书。
1、本站资源针对会员完全免费,站点中所有资源大部分为投稿作者付费教程,切勿轻易添加教程上除本站信息外的任何联系方式,谨防被割,如有疑问请随时联系客服。
2、本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。




×