‹  返回课程

第2课:拜编译器为师

课文
阅读量:680
技术范畴
故意犯7种错误,倒推“Hello World”程序各行的基本含义与作用
课前导言
这节课最主要的,就是认真犯错。
第2课:拜编译器为师
故意犯错,从出错中学习

1.理解编译器的作用

我们使用C++等语言写代码,机器(电脑的CPU)并看不懂,需要一个编译器,将它一次性“翻译”成机器语言。

在本课程中,我们使用浏览器访问“onlinegdb 即:在线GDB”页面在线编写、编译、运行、调试程序 (GDB是一款调试器的名称)。

当我们写好代码;点击“Run”按钮时,代码被上传到该网站的服务器上编译。如果代码不存在语法错误,就在服务器上运行,网站将运行结果传回我们在看的网页页面。如果存在语法错误,编译将失败;这次网站将失败的诊断原因传回页面。

这节课,我们就是要故意制作“编译错误”,从错误中倒过来学习,能让你心中有数,将来碰上必然会碰上的编译错误时,至少能保持镇定。

2.破坏一:代码中夹塞汉字字符

尽管有些编译提供扩展支持,但暂时C++的最新标准,并不支持在代码中除注释、字符串之外,出现汉字,包括全角的字符。我们故意制作的第一个错误,就是将代码中的第一个代码字符“#”改为胖胖“#”:


# include <iostream>
...

一不小心,输入法状态切换不对,在代码中不应该出现汉字,以及汉字全角标点符号——特别是全角的空格——然后看到满屏的错误,这是新人大概率要犯的错误。请各位认真学习视频课程的相关内容,掌握如何理解、判断、定位、以及如何解决这一类的问题。

3. 破坏二:乱改include < > 中的内容

恢复前面的”#”,然后把同一行原来的“iostream”任性地,甚至带有“侮辱性”地加上“2”,变成:


#include <iostream2>

观察编译出错信息:

fail error: iostream2 No such file or directory

大意是: 没有 iostream2 这个文件或目录。

如果你不懂这几个单词,一定要用电子词典查阅一下。事实上C++编译器能给出的出错信息涉及到的单词量并不多,常用更是那么几个;所以哪怕英文差,只要每次遇上不懂的词都上网,比如必应词典在线翻译下,对看懂编译错误,增强解决能力当然大有帮助。

这就倒过来提示我们:原来正确的那“iostream” 肯定是对应到一个存在的文件(或目录)。由于我们使用在线编译,因此这个文件在onlinegdb的服务器上,未来我们在自家的机器安装C++编译器,就会真正拥有这个文件。通常我们把这类文件称为“头文件”,而“include <头文件>”的作用是“包含某个头文件”。

4. 破坏三:把魔爪伸向“std”

这次是将第4行:


using namepsace std;

直接删除。然后就发现编译器埋怨说不认识原来第5行的“cout”这个符号,具体出错内容是:

error : 'cout' was not declared in this scope.

同样的问题发生成“endl” 这个符号上。

出错的关键信息是“not declared”,意思是“没有声明、没有公告”。有“std”在,编译器就认识“cout”和“endl”,没“std”在,编译器就翻脸不认人。所以我们把“std”比喻成是“cout”和“endl”两个小弟的大哥。

大哥在的这一行“using namespace std;”,相当于是大哥向编译器打个招呼:“后面有几个小弟,如果你不认识他们,那就都当作是我的手下即可……”

删除“using namespace std;”,两个小弟在编译器眼里,就身份可疑了,埋怨它们是“was not declared in this scope”,即:“在当前这个范围内,它们没人介绍、引荐。”当然,编译器其实没那么江湖气;“declare”的真正解释是“宣布”、“宣告”、是“声明”。通常我们使用最后一种“声明”。

“namespace”中有个非常熟悉的单词“name / 名字”。“namespace”通常被翻译为“名字空间”或“命名空间”。

“cout”和“endl”之所以是“小弟”,就因为它们的名字在江湖中流传的“范围”不够宽广。而大哥“std”是一个“名字空间/namespace”,它底下“罩”着很多符号“小弟”。“std”全称是“standard / 标准”的意思。没错,它就是C++语言中江湖中的第一大哥:“标准库”。

纯粹的一门编程语言和一门自然语言一样,只是让学会这门语言的人拥有一种“描述能力”。标准库则是这门语言附加带上的功能库。类似你想使用英语念诗,那么你需要英语这门语言(单词、语法、句式等),你最好还拥有一套《莎士比亚全集》,否则你就只能全部自己写诗了。但哪怕你英语八级,你的写诗能力可能也一般而已;所以库的作用很大。库可以来自C++官方,也可以来自第三方,但官方库应该优先学习。

用这个比喻,你也可以将“using namespace std; ”这一行的作用,理解为是在向编译器宣告:“以下凡是你没听过的话,都出自鲁迅……”。

视频里没讲,但我们补充一下:如果不想恢复删除的第4行,也有办法使用“cout”和“endl”,那就是每当用到二者时,都强调一下它们是“标准库(std)”的人……请看代码:

  std::cout << "Hello World."  <<  std::endl;

“::”可以读成“的”,std::cout 就是“std的cout”;“std::endl”就是“std::endl”。未来还会学习C++中的其它几种“的”的表达。但这里的“::”,主要是用于区分或者划分“名字”的范围。类似于如果你小学有一个“李国庆”同学,初中有一个“李国庆”同学。那么在某些场合下,你可能就不得不这样区分他们:“小学同学::李国庆”或“初中同学:::李国庆”。

最后,大哥“std”这个符号,本身在哪里被引入呢?正是前面我们乱改的那一行:“#include ”。编译器遇上“#include ”这样的指示,就会直接打开那个文件读入其内容加以编译。“std”在“iostream”这个文件中有声明。

5. 破坏四:天底下所有C/C++程序员都会犯的错……

那就是,不小心把“main”写成“mian”。

“main”是“主要”的意思。我们写的程序,编译之后,最终是要在操作系统(比如Windows、Linux)中运行。程序可以很多功能,并且在术语上被称为“函数”,操作系统色迷迷地,兴奋地说:“哇,好多……我先临幸……”

正经点,操作系统应该是想说“我先执行哪个功能?”,更正经点,应是“我先调用哪个函数?”。C/C++程序通常约定代码中名为“main”并且符合某些条件设置的函数,称为“主函数”,作为供操作系统调用的入口。

将它不小心写成“mian”……会招来编译器一大堆抱怨,但最要的是最下面的一句:

undefined reference to 'main'.

之前学习的“no declared”和这里的“undefined”是我们最容易遇上的两类错误。前者说“某个符号未声明”本质是:“你的名字我不认识……”,后者是说“某个引用(reference)未定义(undefined)”本质是:“(我听过你的芳名,但是)你的身体我找不到。”

请大家自行理解这其中的意味吧。

C++编译器当然默认就知道一个C++程序需要一个长成某个样子的“main()”函数——他听过它的芳名——但当代码全部编译完成后,编译器却发现根本没有传说中的“main()”主函数,于是报错。

今天我们自己写一些函数或数据,当然也可以“声明”它们。我们写的符号没有“main”这么有名气,必然主动声明编译器才能认识它们。假设我们光声明没有定义(实现),那么就会看到和此刻差不多的报错了。

6. 破坏五:乱改代码中对象名字

这次演示的是修改两位小弟“cout”和“endl”的名字……你应该能想到了,也会出错。代码中大多数符号,都必须有声明(名字)也有定义(身体)。

7. 破坏七:删除一个 }

删除代码中的最后一个字符 “}”。编译器直接报:

“main.cpp:10:12: error : exepcted '}' at end of input. ”

你要学会的是这个词“expected”,期望的……。函数(本例中是主函数)的主要实现部分,需要放在一对 {} 之内。删除后面那个,这可真是很低级的错误,所以编译器波澜不惊地说“我在这里这里(第10行第12列),期待有一个右花括号啊……”

那为什么要举这么一个错?原因是,只有一层{}是不容易出错,但将来代码复杂了,会有一层又一层的 {} 层层嵌套,到时候就容易出现括号不匹配的情况了——因此,代码格式很重要;好的格式排版至少有一个好处:层层嵌套的括号匹配关系,要做到一目了然。

7. 破坏六:删除“return 0;”

只有主函数拥有这样的特权:明明需要返回一个数字,但如果就是在代码的最后返回0,那就可以省略。

举这一点只是为了后面演示中,我们的主函数基本都是返回0,此时我将懒得写它——反正合法。

课后补充
试试看,你还可以制作出哪些错误……