藁城网站建设,电脑网站 手机网站 微信网站,国外购物平台有哪些,企业网站建设广州前言
很多编程语言的语法中都有关于声明和定义的概念#xff0c;这种概念一般会应用于函数或变量的创建和使用中#xff0c;但是为什么要这么做#xff1f;
以C语言为例#xff0c;一些书籍或教程会要求读者在程序文件开头写上函数和变量的声明#xff0c;然后再在后面对…前言
很多编程语言的语法中都有关于声明和定义的概念这种概念一般会应用于函数或变量的创建和使用中但是为什么要这么做
以C语言为例一些书籍或教程会要求读者在程序文件开头写上函数和变量的声明然后再在后面对其进行定义对于变量也可以叫初始化。这不免让人感到有一丝疑惑为什么要这样做我能不能先定义再声明或者我能否不声明直接定义
事实上这样做很多时候也是可以的但是为什么呢
为此我们需要明确一些基本的概念。
编译和链接
以C和C为例如果你正在使用某个IDE进行编程那么你应该可以发现在你写好一个程序后需要将它运行之前往往需要先进行构建有的IDE里面写的是生成或build然后经过一段时间的等待后你就会获得一个可执行程序之后你就可以运行这个程序文件得到想要的效果。
这个过程中发生了什么呢一般情况下这个过程分为了三步——预处理、编译、链接。预处理过程一般是C和C的特色你所见到的那些带#的语句一般被称为宏而这些宏将会被预处理器进行预处理。通常而言你只需要知道#define将被定义的内容用定义的内容进行替换和#include将被引用的文件全部原样复制到此两个宏就够了。
不过预处理并不是我们讨论的焦点。
编译一般发生于预处理之后一般而言当预处理结束后所有有用的.h文件都已经通过#include被原样复制到了.c文件中了所以整个工程现在就只剩下一堆.c文件。而编译器会检测每个.c文件然后将它们编译称为对应的汇编文件再然后将汇编文件翻译成二进制文件。不过一般而言翻译成为汇编文件这个过程都是被隐藏的至少如果不单独设置你是无法看到生成汇编文件这个过程。不过没关系这样也可以简化我们的思路所以直接将编译过程理解为从.c文件到二进制文件这个文件后缀根据编译器和系统的不同可能有所差异一般是.o或.obj当然具体是什么后缀都不重要。
编译结束后显然我们会得到一堆二进制文件它们和之前写的.c文件一般是一一对应的。但是很显然这些二进制文件还不是可执行程序而且它们太分散了。这个时候就需要链接器进行工作链接器会根据链接表将所有二进制文件进行重组和拼装最后形成一个完整的可执行文件。
声明和定义
那么声明和定义在这里有什么用呢
为什么要声明
现在我们假设一个工程中有两个.c文件一个是main.c里面放了一个空的main函数此外什么也没有。另一个是hello.c里面放了一个void SayHello()函数并引用了stdio.h文件这个函数的功能是输出一个Hello World。
显然hello.c里面实现了对SayHello函数的定义也就是说我们的工程中已经实现了SayHello这个函数此时我们是否能够在main函数中调用它呢
如果你尝试直接在main函数中调用SayHello然后分别对main.c和hello.c进行编译一般IDE会有单独的编译选项右键单击某个c文件就可以选择而且这个编译事实上还是包括了预处理过程的你会发现对main.c进行编译的时候会出现报错。
编译器会提示你没有对SayHello函数进行定义。
但是我们明明在hello.c中进行定义了啊难道是我需要先编译一下hello.c然后再编译main.c事实上你换一下顺序结果也是一样。
因为编译器是没有记忆的当他编译完一个文件就会马上忘记它在这个文件里都发现了什么东西所以哪怕它刚刚才在hello.c里面编译了SayHello的定义但是现在它在编译main.c它就已经忘记了这回事了。
那么我们有什么办法可以解决这种情况呢
第一种方法就是在main.c的前面加上对SayHello的定义注意尤其一定要在main函数的前面。有人会问为什么一定要在前面后面不行吗事实上确实不行因为当编译器发现一个它从来没见过的函数调用时就会马上报错不会那么智能地再在后面找一遍你是不是定义在后面了所以唯有在调用之前就定义好他才知道你有好好定义这个函数然后它就不会闹了。
但是显然如果我希望在多个文件里面调用SayHello函数那么这个方法就不起效了。
所以我们就有了第二种方法在main.c文件前面加上一句对SayHello的声明。声明只需要写上函数返回值、函数名和函数参数表就可以了不需要写完整的实现。声明其实是程序员对编译器的保证意思就是“我保证我在其它某个地方肯定会写这个函数的定义的”然后编译器会相信程序员虽然你并没有实际在这个文件中定义这个函数但是当你调用这个函数的时候编译器不会报错。注意声明也需要写在调用的前面逻辑和第一种方法一样。
现在你大概理解了声明的意思了而一般情况下声明会被写在一个.h文件中要使用某个函数之前只要#include这个文件并且将它对应的.c文件包含在工程内即可。
注意除了#include你一定也要将它对应的.c文件包含在工程内因为.c文件里包含了对这些函数的定义。
为什么要定义
没有定义显然是不行的否则这个函数就是一个空有名字和输入输出的不明物很显然这是程序员的疏忽或像我们故意造成的错误是需要被发现检测出来的。
现在我们做个简单的小实验还是刚才的工程但是我们将hello.c里的SayHello注释掉如果你不怕麻烦想要更好的体验可以直接删掉hello.c或将它移出工程此时我们再次编译hello.c和main.c你会发现编译器仍然没有报错。
是不是有点疑惑现在明显没有SayHello的定义但是为什么编译器不报错呢
很显然是因为编译器非常相信你你在main.c中声明了你一定会定义SayHello的所以编译器就理所当然地相信了你而没有记忆的它显然是无法发现自己被骗了。而hello.c文件里什么都没有那么自然也不会有什么错如果你直接将其移除了工程就更加不可能报错了。
但是这并不代表你能一直骗它我们知道编译结束后还得链接才能得到可执行程序不过IDE一般没有单独的链接按钮所以我们得使用构建当按下这个按钮后整个工程都将完整经历完前面说的三个过程——预处理、编译和链接。其中预处理和编译仍然是刚才我们的编译器进行的它和刚才一样没有发现任何问题。
但是你骗不了链接器因为它会对程序进行重新组装但是在组装的过程中它会发现有一个本来应该出现的函数定义却在此刻不见了然后它会发出一个L字母开头的报错唯有你在工程中重新创建了这个函数的定义并重新构建才可以消除。
总结
这里用C语言的函数声明与定义解释了为什么要声明和定义以及它们有什么用用相同的逻辑它也可以扩展到变量以及其它语言的声明与定义中。