最近一直有同学问为何会出现这种编译错误,既然需求如此之大,我就写篇文章解释一下吧。
先解释一下,代码在编译的时候,你的电脑中发生了一件什么事吧。
编译指的就是把人类可以理解的文本程序“翻译”成电脑可以识别执行的指令清单,充当翻译官的角色的就是编译器。
一般来说,童鞋使用的devc++内部包含了g++/gcc/c++等等,这些都是编译器,可以在devc++的安装目录里找到,如图
其实编译器就是一个可以运行的程序,它接受你的源代码作为输入,然后输出的就是它的翻译结果啦~~
但是呢,编译器小姐姐是傲娇属性爆棚的那种人~一旦你的源代码中有语法错误,她就不会给你翻译(编译),但是小姐姐人辣么好,她会把你的错误之处向你指出。
所以学习看编译信息就是和小姐姐交朋友的第一步啦~如果你哪个编译信息看不懂,可以把它丢到搜素引擎里去搜搜,一般来说看懂了就能知道哪儿出错了。
而今天的主题是,这种编译错误是什么意思,然后要如何解决这个问题。
泥萌回忆一下,在学着把代码拆分到不同文件里然后用include合并到一起之前是不是从来没见过这种错误呢?
include的作用其实非常简单,就是把你include的文件内容替换你的include那句语句。这也就解释了为何不能A include B然后B include A,想啊,这样替换下去不就无限循环了嘛;以及为什么不能多次include同一个文件,想啊,这不就相当于你的程序里可能会出现两个一毛一样的函数、变量嘛。
这一替换是在编译之前发生的,所以小姐姐在编译时就可以找到你定义在其他文件(file2.cpp)里的函数,从而允许你在当前文件里使用(file1.cpp)。
这本来是极其愉快而简单的一件事,可是泥萌的助教小姐姐不让泥萌include cpp文件。
因为啊,通常来时被incude的叫“头文件”,而头文件一般包含的是类的定义、extern变量声明和函数的声明等等声明性、定义性的东西,而不是具体实现,这样会带来两个显著的好处:
- 保证所有文件使用的是同一个声明,这样修改起来很方便;只需要修改一处,而不用因为工程需求变动(以后泥萌会想吃掉一个叫PM的人的)而去修改分散在每个文件中的定义。是不是时曾相识?对的,这个理念在泥萌学用define来对常量进行宏定义就出现过了。
- 方便支持预编译头文件,这个的意思是,当泥萌的工程成长到一个极其大的规模时,编译时间会变得非常感人,因为小姐姐的翻译速度也是有限的啊。所以为了减少小姐姐的工作量,我们可以把代码拆开,然后就可以分开编译,最后把它们并到一起,一旦有细微的改动,只需要修改需要修改的函数实现,然后重新编译改动过的文件,再把它们合并到一起。
请泥萌仔细地看一下第二点,然后我来解释一下为什么头文件不应该包含具体实现。
一般具体实现放在cpp文件里,这也是为何我们称后缀为.h的文件为头文件而不叫.cpp文件头文件。
假设你修改了某个函数的具体实现,那么显然,包含了这个修改的文件必须重新编译对吧,因为你会include cpp文件,那么include了这个cpp文件的文件也得重新编译,假设还有其他文件include你include这个cpp文件的cpp文件,这个文件也得重新编译,再假设...
这样的话第二点的存在意义在哪??
解决方案就存在于被include的头文件只应该包含声明和定义。
但是如果只包含只有声明文件的头文件,会发生什么呢?
会发生——小姐姐虽然找得到函数的声明,但是她找不到函数的实现!这就是造成文前那个问题的元凶!
let's take a closer look on what 小姐姐 has said.
undefined reference to 'YourFunction'
请大家体会一下undeclared(声明)和undefined(定义)的区别!
让我来画个图:
(箭头的意思代表include)
小姐姐看到你让她帮你编译一下main.cpp,于是她在编译开始前把declare.h放到main.cpp里。
一切看起来都非常和谐,因为一切在main.cpp中调用的函数、使用的自定义变量类型的声明都可以找到~
小姐姐极其开心呐~
但是当小姐姐开始编译时,听听她怎么说:“伦家当场就懵逼了好嘛qaq”
因为她找不到你函数的实现呐,你函数的实现在define.cpp里,这件事情她怎么可能会知道呢?
除非你告诉她。
所以解决方案其实非常简单,修改你的编译命令,把define.cpp的所在告诉泥萌可怜的小姐姐!
好的,我忘记先向泥萌解释编译命令是啥了。
编译命令就是你告诉小姐姐应该按照什么样的顺序、什么样的方式、什么样的程度来进行编译,要知道其实虽然一直把编译比作翻译,其实编译过程有很多个部分,请参考reference中的Linux GCC常用命令
具体要如何修改呢?如果你是面向命令行编程(虽然我觉得。。泥萌目前大概是不会面向命令行编程的),只需要修改一下编译命令即可:g++ main.cpp define.cpp -o main.exe
这个命令(原来是g++ main.cpp -o main.exe,注意区别)就告诉编译器小姐姐,我还有一个define.cpp文件,如果你需要找某些函数的实现,你可以考虑一下看看define.cpp里面有没有对应的实现~
但是要如何才能直接把你的编译命令告诉编译器小姐姐呢?
follow the instructions,老司机要发车啦~
- 首先,同时按住win+r,输入cmd,然后回车
- 对着黑乎乎的窗口输入g++,去唤醒小姐姐
如果你看到类似的输出,说明小姐姐被唤醒哒,跳到第4步,否则继续 - 如果提示的是command not found,说明小姐姐还处在devcpp的魔爪下,让我来指导你来拯救她吧!
找到你的devcpp的安装目录,这个具体位置因人而异,一般在C:\Program Files (x86)下,如果你找不到,请百度一下devcpp的安装目录。。这个我难以帮忙。。
在安装目录下有一个MinGW(64)文件夹,点击进入,里面有一个bin文件夹,我的小姐姐就安静地躺在里面呐~明眼的你能看到她吗?
总之,找到这个路径
ctrl+c复制一下。
把这个路径添加到系统变量path里去,具体看http://jingyan.baidu.com/article/8ebacdf02d3c2949f65cd5d0.html
添加之后,再从第一步开始,你就会发现小姐姐对你的召唤有反应了!!
- 找到你的源代码文件所在的文件夹,在空白的地方先按住shift,再单击鼠标右键,弹出的窗口中有
在此处打开命令端口,点击,然后又会出现黑乎乎的窗口(写到这才意识到,你们应该认识这个窗口的。。) - 输入g++ main.cpp define.cpp -o main.exe回车
这就是在直接调用编译器来编译程序,得到main.exe的结果就是可执行文件,可以双击运行。
但是你可以在命令行里继续输入main.exe回车,就可以直接执行main.exe了!
nice and quick! - 问题就解决了!我保证你不会再看到文前的那种错误了,如果有其他错误,那就和这个问题无关了。
写到这里,我提供了一个解决方案,就是手动写编译命令,告诉小姐姐编译时去考虑define.cpp,在具体情况下,比如泥萌的作业,你要把median.cpp student_info.cpp fails_list.cpp等等放到编译命令里去再编译。
写这篇文章的意义就达成了。
但我还是想多说一些导向性的东西,比起这些东西,技术细节其实并不重要。
- 编译过程(预处理、编译、链接)是个很值得花时间理解的东西,请自行找些资料读读,以后课程会有《编译原理》,教你如何自己写一个编译器(不过我旦此课水平令人忧心忡忡)
- 以后随着你需要考虑的文件越来越多,每次编译写编译命令就是个令人生厌的过程了,你会经历:手写.bat批处理文件、使用Makefile、回到ide的怀抱(强烈推荐VS2017)
- 请大家遇到问题之后,先上网搜搜,相信我,大多数遇到的问题前人都遇到过,比如这次的问题,搜undefined reference就能搜到解释的;之所以提出这一点不是说不欢迎大家来问问题,但是这样做显然会节约两者的时间对吧~嘻嘻,表扬
大概写了三个小时。。第一次写这种东西。。希望对大家有点帮助吧,写模电作业去qaq。。。我的周末。。真是充实呐苦笑。。
reference(further reading materials!):