C文件与头文件(即.h)有什么不同之处?

简单来说,要了解C文件和头文件(即.h)的区别,首先需要了解编译器的工作过程。一般来说,编译器会做以下几个过程:

1.预处理阶段

2.词汇和语法分析阶段

3.在编译阶段,先编译成纯汇编语句,再汇编成CPU相关的二进制代码,生成各种目标文件(.obj文件)

4.在连接阶段,对每个目标文件中的每段代码进行绝对地址定位,生成与特定平台相关的可执行文件。当然最后也可以使用objcopy生成纯二进制代码,也就是去掉文件格式。信息。(生成 .exe 文件)

编译时编译器是基于C文件的,也就是说,如果你的项目没有C文件,那么你的项目将无法编译,链接器是基于目标文件的,它会一个或多个。目标文件用于重新定位函数和变量以生成最终的可执行文件。PC上的程序开发一般都有一个main函数,就是各个编译器的约定。当然,如果你自己编写链接脚本,你可以将main函数作为程序入口!!!!

主 .c 文件 目标文件 可执行文件

有了这些基础知识,我们言归正传,为了生成最终的可执行文件,我们需要一些目标文件,也就是C文件,而这些C文件需要一个main函数作为可执行程序的入口,那么我们将从一个C文件开始,假设这个C文件的内容如下:

#包括

#include “mytest.h”

int main(int argc, char **argv)

{

测试 = 25;

printf(“test……..%d\n”,test);

}

mytest.h头文件内容如下:

智力测验;

现在以这个例子来解释编译器的工作:

1.预处理阶段:编译器以C文件为单位,首先读取C文件,发现第一、二句包含一个头文件,会在所有搜索路径中查找这两个文件。,找到后会处理对应头文件中的宏、变量、函数声明、嵌套头文件包含等,检测依赖关系,进行宏替换,看是否有重复的定义和声明。这些文件中的所有内容都被扫描到当前的 C 文件中,形成一个中间“C 文件”

2.在编译阶段,在上一步中,将头文件中的测试变量扫描成一个中间C文件,然后测试变量就变成了这个文件中的全局变量,所有的变量和函数这个中间C文件分配空间,把每个函数编译成二进制代码,根据特定的目标文件格式生成目标文件,在这种格式的目标文件中对每个全局变量和函数进行符号描述,并将这些二进制转换为代码按照一定的标准组织成目标文件

3. 在连接阶段,将上一步生成的每个目标文件都连接起来,根据一些参数生成最终的可执行文件。主要工作是对每个目标文件的函数、变量等进行重定位,相当于将一个目标文件中的二进制代码按照一定的规范组合成一个文件,然后回到正题C文件和头文件中写什么:理论上,C文件和头文件中的内容,只要是C语言支持的,什么都可以写,比如写一个函数体在一个头文件中,只要在任何一个C文件中包含这个头文件,函数就可以编译成目标文件的一部分(编译是基于C文件,如果不是这个头文件包含在任何C文件中,这段代码是没用的),可以在C文件中进行函数声明、变量声明、结构体声明,都没有问题!!!那么为什么一定要分为头文件和C文件呢?

而为什么函数、变量声明、宏声明、结构声明一般都在头文件中进行呢?而在C文件中定义变量,函数实现呢?? 原因如下:

1.如果一个函数体在头文件中实现,那么如果在多个C文件中引用,同时编译多个C文件,生成的目标文件链接成一个可执行文件,并在每个引用这个头文件的C文件生成的目标文件中,都有一份这个函数的代码。如果这个函数没有定义为局部函数,那么在连接的时候会发现多个相同的函数。报错

2.如果在头文件中定义了一个全局变量,并且给这个全局变量赋了一个初始值,那么引用这个头文件的多个C文件中也存在同一个变量名的副本。关键是这个变量被赋了初值,所以编译器会把这个变量放到DATA段中,最后在连接阶段此文档有宏是什么意思,DATA段中会有多个相同的变量,它无法将这些变量统一为一个变量,即只给这个变量赋值一个空格,而不是多个空格。假设在头文件中没有给这个变量赋初值,编译器会把它放到BSS段中,链接器只会为BSS段中的多个同名变量分配一个存储空间。

3.如果在一个C文件中声明了宏、结构体、函数等,那么如果我想在另一个C文件中引用相应的宏和结构体,就得重新做重复的工作。如果我在C文件中改了一个A声明,然后忘记改其他C文件中的声明,这不会是什么大问题,程序的逻辑会变得难以想象,如果你把这些公共的东西放在一个头文件中,如果要使用它的C文件,只需要引用一个就可以了!!!会不会很不方便,当你想改变一个语句时,你只需要移动头文件。

4.在头文件中声明结构、函数等。当你需要将你的代码封装到一个库中,让别人使用你的代码,而你又不想发布源代码时,人们如何使用你的库呢?? 也就是如何使用你库中的各种功能?? 一种方式是发布源代码,其他人可以随意使用。另一种方式是提供头文件,其他人会从头文件中查看你的函数原型,这样人们就知道如何调用你编写的函数,就像你调用 printf 函数一样。同问,里面的参数是什么?? 你怎么知道?? 不是看别人头文件里的相关声明!!!当然这些东西都变成了C标准了,就算不看别人的头文件,

c语言中.c和.h文件的混淆

本质上没有区别。只是一般性:.h 文件是一个头文件,其中包含函数声明、宏定义、结构定义等。

.c 文件是包含诸如实现、变量定义等功能的程序文件。而且后缀是什么并不重要,但是编译器默认会对带有某些后缀的文件采取某些动作。您可以强制编译器将带有任何后缀的文件视为 c 文件。

编写这两个单独的文件是一种很好的编程风格。

另外,假设我在aaa.h中定义了一个函数声明,然后我在aaa.h同目录下创建aaa.c,aaa.c定义了函数的实现,然后主函数所在的位置#include这个 aaa.h 在 .c 文件中,然后我可以使用这个函数。

当 main 运行时,它会找到定义该函数的 aaa.c 文件。

这是因为:

main函数是标准C/C++的程序入口,编译器会先找到函数所在的文件。

假设编译器在编译 myproj.c(其中包含 main())时,发现它包含了 mylib.h(其中声明了函数 void

test()),然后编译器会根据预设的路径(包括路径列表和代码文件所在的路径)搜索同名的实现文件(扩展名.cpp或.c,本例中为mylib) . .c),如果找到文件,并且在其中找到函数(在这种情况下为无效)

test()) 实现代码,继续编译;如果在指定目录下找不到实现文件,或者在文件和后续包含文件中都找不到实现代码,则返回编译错误。其实include的过程可以完全“看成”就是一个文件拼接的过程。如果声明和实现分别写在头文件和C文件中,或者两者同时写在头文件中,理论上没有本质区别。

图片[1]-C文件与头文件(即.h)有什么不同之处?-老王博客

以上就是所谓的动态方法。

对于静态模式,基本上所有的C/C++编译器都支持一种叫做静态链接的链接模式,也就是所谓的静态链接。

这样,我们所要做的就是编写包含函数、类等声明的头文件(ah,bh,…),以及它们对应的实现文件(a.cpp,b.cpp,… ),编译器会将其编译成静态库文件(a.lib,b.lib,…)。在后续的代码复用过程中,我们只需要提供对应的头文件(.h)和对应的库文件(.lib),过去的代码就可以使用了。

与动态方法相比,静态方法的优势在于实现代码的隐藏,即C++提倡的“接口在外,实现代码不可见”。促进库文件的转发。

如果难题中最难的部分是基本概念,那么许多人会反对它,但事实确实如此。我高中学物理的时候,老师的重点是概念——概念必须要理解,所以难的问题就变成了简单的问题。如果你能清楚地分析出一个物理问题中有几个物理过程,并且每个过程都遵循哪个物理定律(如动量守恒定律、牛二定律、能量守恒定律),那么就很容易列出这个过程的方程了根据定律,N个过程必须是N个N元方程此文档有宏是什么意思,问题就解决了。即使在高中物理竞赛题中,最难的部分是:

(1),混淆你的概念,以至于你不能分析几个物理过程,或者一个物理过程遵循的物理规律;

(2),还有高阶方程,列出来无法解方程。后者已经是数学的一个范畴,所以最难的是把握一个清晰的概念;

程序设计也是如此。概念清楚的话,基本没有问题(数学上会比较难,比如算法的选择,时空与效率的权衡,稳定性与资源的平衡)。然而,要掌握清晰的概念并不容易。以下面的例子为例,看看你是否有一个非常清晰透彻的理解。

//啊 void foo(); //ac #include “ah”//我的问题是:这句话该说还是不说?无效 foo(){ 返回;} //main.c #include “ah” int main(int argc, char *argv[]) {foo(); 返回0;}

对于上面的代码,请回答三个问题:

(1)ac 中的短语#include “ah” 是多余的吗?

(2)为什么你经常看到xx.h对应于xx.c​​中的include?

(3)如果不是用ac写的,编译器会不会自动将.h文件的内容和同名的.c文件绑定?(否)

(请仔细思考以上3个问题10分钟,不要担心下面的解释。:)你想的越多,理解就会越深。

好吧,是时候了!请忘记上面的3个问题,以及你对它们的想法,慢慢听我说。正确的概念是:从C编译器的角度来看,.h和.c都是浮云,即使重命名为.txt和.doc,也没有太大区别。换句话说,.h 和 .c 不一定相关。一般在同名.c文件中定义的变量、数组、函数的声明放在.h中,需要在.c之外使用的声明。这个说法有什么用?只需在需要这些声明的地方轻松引用即可。因为宏#include “xx.h”的实际含义是删除当前行,将xx.h的内容插入到当前行。由于写这些函数声明的地方很多(每一个调用xx.c中的函数的地方,都必须在使用前声明),所以使用宏#include“xx.h”可以简化很多行代码——让预处理器自己替换它。也就是说,xx.h其实只是为了调用xx.c中函数声明需要写的地方(可以少写几行),至于include.h文件是谁,是不是.h 或 .c,或使用 this 。h 和 .c 同名没有必然关系。c需要写(可以少写几行),至于include.h文件是谁,是.h还是.c,还是用this。h 和 .c 同名没有必然关系。c需要写(可以少写几行),至于include.h文件是谁,是.h还是.c,还是用this。h 和 .c 同名没有必然关系。

所以你可能会说:嗯?然后我通常只想调用xx.c中的某个函数,但是包含xx.h文件。宏替换后不是有很多无用的声明吗?是的,它确实引入了很多垃圾,但它为你节省了大量的笔墨,整个布局看起来更干净。这是事实。反正声明多了(.h一般只用来放声明,不放定义,看我的书《过马路,左顾右盼》)也是无害的,也不影响编译,何乐而不为?

回过头来看上面的三个问题,不难回答吧?

答:不一定。在这个例子中显然是多余的。但是如果.c中的一个函数也需要调用同一个.c中的其他函数,那么这个.c中往往会包含同名的.h,所以不用担心声明和调用顺序(C语言需要必须在使用之前使用它)。声明,并包含同名。H 通常放在.c) 的开头。许多项目甚至将这种书写约定作为代码规范来标准化清晰的代码。

答:1 已经回答了。

答:没有。问这个问题的人肯定是不清楚这个概念,或者只是想在水里钓鱼。很烦人的是,国内很多考试都有这么烂的题,生怕别人有一个明确的概念,肯定会迷惑考生。

弄清楚语法和概念说起来容易做起来难。秘诀三:工作不要头晕,多花点时间思考,多看书;

看书读好书,问人问强人。坏书和坏人会给你一个错误的概念,误导你;

勤奋能补短板是一种很好的培养,每一次努力都值得天赋;

(1)库函数是通过头文件调用的。很多时候源代码不方便(或者不允许)发布给用户,只要给用户提供头文件和二进制库,用户只需按照头文件中的说明调用库函数的接口声明即可,无需关心接口是如何实现的,编译器会从库中提取相应的代码。

(2)头文件可以强制执行类型安全检查。如果一个接口的实现或使用方式与头文件中的声明不一致,编译器会指出错误。这个简单的规则可以大大缓解程序员调试和纠正错误的负担。

头文件用于存储函数原型。

头文件与源文件有什么关系?

问题其实是说已知的头文件“啊”声明了一系列函数(只有函数原型,没有函数实现),而这些函数都是在“b.cpp”中实现的,那么如果我要使用“c.cpp” ” “ah” 中声明的”b.cpp” 中实现的这些函数通常在”c.cpp” 中使用#include

“啊”,那么c.cpp是如何在b.cpp中找到实现的呢?

其实.cpp和.h文件名没有直接关系,很多编译器都可以接受其他的扩展名。

谭浩强老师在《C编程》一书中提到,编译器预处理时,要对#include命令进行“文件包含处理”:将headfile.h的全部内容复制到#include

“头文件.h”。这也解释了为什么许多编译器不关心这个文件的后缀是什么——因为#include 预处理是一个“复制和插入代码”的工作。

程序在编译的时候不会在b.cpp文件中寻找函数实现,只有在链接的时候才会去寻找。我们在 b.cpp 或 c.cpp 中使用 #include

“啊”其实是引入了相关声明,这样编译就可以通过了,程序并不关心在哪里以及如何实现。编译源文件后,会生成一个目标文件(.o 或 .obj 文件)。在目标文件中,这些函数和变量被视为符号。链接的时候需要在makefile中指定需要链接哪个.o或者.obj文件(这里是b.cpp生成的.o或者.obj文件),这个时候链接器会去这个.o或 .obj 文件 找到 b.cpp 中实现的函数,并将它们构建到 makefile 中指定的可执行文件中。

(很重要)

在VC中,一堆情况下不需要自己编写makefile,只需要在项目中包含所有需要的文件,VC会自动为你编写makefile。

通常,编译器会在每个 .o 或 .obj 文件中查找所需的符号,而不是只查找文件或不查找文件。因此,如果在几个不同的文件中实现了相同的功能,或者定义了相同的全局变量,在链接的时候会提示“重新定义”。

希望对大家有帮助~

学习C/C++编程知识,想成为更好的程序员,或者学C/C++有困难,可以关注+私信编辑【C/C++编程】作者的C语言C++零基础编程学习圈,有不仅学习视频和文档,还有更多志同道合的朋友。欢迎转行学习编程的伙伴。与你沟通会比你自己思考成长得更快!

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论