简单来说,要了解C文件和头文件(即.h)的区别,首先需要了解编译器的工作过程。 一般来说,编译器会做以下几个过程:
1.预处理阶段
2. 词法分析阶段
3、编译阶段,先编译成纯汇编语句,再编译成与CPU相关的二进制代码,生成各个目标文件(.obj文件)
4、在连接阶段,对每个目标文件中的每段代码进行绝对地址定位,生成特定平台相关的可执行文件。 当然也可以用于最后生成纯二进制代码,即去掉文件格式信息。 (生成.exe文件)
编译器在编译的时候是以C文件为单位的,也就是说,如果你的工程中没有C文件,那么你的工程是不会被编译的,而链接器是以目标文件为单位的,它会每个目标文件重新定位函数和变量以生成最终的可执行文件。 PC端的程序开发一般都有一个main函数。 这是每个编译器的约定。 当然,如果你自己写链接描述文件,你可以使用main函数作为程序入口! ! ! !
(main.c文件目标文件可执行)
有了这些基础知识,我们言归正传,为了生成最终的可执行文件,我们需要一些目标文件,也就是C文件,而这些C文件需要一个main函数作为可执行程序的入口,那么我们将从一个C文件开始,假设这个C文件的内容如下:
#
# “。H”
int main(int argc, char **argv)
{
测试 = 25;
("测试............%d\n",测试);
}
.h头文件内容如下:
内部测试;
现在以这个例子来说明编译器的工作:
1.在预处理阶段,编译器以C文件为单位,首先读取C文件,发现第一句和第二句包含头文件,就会在所有搜索路径中搜索这两个文件,并找到它们,它会处理对应头文件中的宏、变量、函数声明、嵌套头文件包含等,检测依赖关系,进行宏替换,看是否有重复的定义和声明,最后将那些文件放入All这些东西被扫描到当前的 C 文件中,形成一个中间“C 文件”
2.在编译阶段,在上一步中,相当于把头文件中的test变量扫描到一个中间C文件中,那么test变量就变成了这个文件中的全局变量,此时所有的中间C文件的所有变量和函数分配空间,将每个函数编译成二进制代码,并根据特定的目标文件格式生成目标文件。 在这种格式的目标文件中,对各个全局变量和函数进行符号描述,并将这些二进制代码按照一定的编译标准组织成一个目标文件
3. 在连接阶段,根据一些参数将上一步生成的目标文件连接起来,生成最终的可执行文件。 主要任务是将各个目标文件的函数、变量等重新定位,相当于将二进制代码按照一定的规范组合成一个文件,再回到C文件写什么的话题头文件:理论上来说,只要C文件和头文件里的内容是C语言支持的,不管写什么都可以,比如你把函数体写在头文件里,只要这个头文件包含在任何C文件中,函数就可以编译成目标文件的一部分(编译是基于C文件的,如果不在任何C文件中如果这个头文件包含在,这段代码没用),你可以在C文件中进行函数声明、变量声明和结构声明,这不是问题! ! !
那为什么一定要分头文件和C文件呢? 为什么函数、变量声明、宏声明和结构声明一般都在头文件中进行? 而在C文件中定义变量,函数实现呢? ? 原因如下:
1.如果一个函数体是在头文件中实现的,如果在多个C文件中被引用,同时编译多个C文件,生成的目标文件会链接成一个可执行文件,每次引用this在头文件的C文件生成的目标文件中,有一段这个函数的代码。 如果这个函数没有定义为局部函数,那么在链接的时候,会发现多个相同的函数,就会报错。
2、如果在头文件中定义了一个全局变量,并为该全局变量赋了初值,那么在引用这个头文件的多个C文件中,存在同名变量名的副本。 关键是给变量赋初值。 因此,编译器会将这个变量放入DATA段。 最后,在连接阶段,DATA段中会出现多个相同的变量。 它不能将这些变量统一为一个变量,即只为这个变量分配一个空间。 而不是多个空格,如果这个变量在头文件中没有赋初值,编译器会把它放在
在BSS段中,链接器只会为BSS段中的多个同名变量分配一个存储空间。
3、如果在一个C文件中声明了宏、结构体、函数等,那么如果我要在另一个C文件中引用相应的宏和结构体,就必须再做一次重复的工作。 如果我改了一个C文件而忘记改其他C文件中的语句,那问题就大了,程序的逻辑会变得你无法想象。 如果你把这些的东西放在一个头文件里,你要用它的C文件,只需要引用一个就OK了! ! !这不是很方便吗? 当你想改变一个语句时,你只需要移动头文件。
4.在头文件中声明结构、函数等。 当你需要把你的代码打包成一个库让别人使用你的代码,而你又不想把源码公布出来,别人怎么用你的库呢? 也就是如何使用你库中的每一个函数? ? 一种方法是将源代码公开,其他人可以随意使用。 另一种是提供头文件,别人可以从头文件中读到你的函数原型,从而知道如何调用你写的函数,就像你调用函数一样。 ,里面的参数是什么? ? 你怎么知道? ? 不是看别人头文件中的相关语句! ! ! 当然,这些东西都成了C标准,即使不看别人的头文件,也能知道怎么用。
C语言中.c和.h文件的混淆
本质上没有什么区别。只是笼统地说:.h文件是一个头文件,里面包含了函数声明、宏定义、结构体定义等。
.c文件是程序文件,包含函数实现、变量定义等内容。 并且后缀是什么并不重要,但是编译器会默认对具有某些后缀的文件采取某些操作。 您可以强制编译器将任何后缀的文件编译为c文件。
将这两个文件分开编写是一种很好的编程风格。
而且,比如我在aaa.h中定义了一个函数声明,然后我在aaa.h的同级目录下创建了aaa.c。 这个函数的实现定义在aaa.c中,然后main函数位于.c文件中的#this aaa.h,然后我就可以使用这个函数了。 Main 会在运行时找到这个定义这个函数的 aaa.c 文件。
这是因为:
main函数是标准C/C++程序的入口,编译器会先找到该函数所在的文件。
假设编译器在编译.c(包括main())时,找到mylib.h(里面声明了函数void test()),那么编译器就会按照预设的路径(路径列表和代码文件所在的路径) )寻找同名的实现文件(扩展名.cpp或.c,本例为mylib.c),如果找到,找到其中的函数(本例为void test()),然后继续编译; 如果在指定目录下找不到实现文件,或者在本文件及后续文件中找不到实现代码,则返回编译错误。 其实这个过程可以“看”成一个文件拼接过程,声明和实现分别写在头文件和C文件中,或者两者同时写在头文件中,理论上是没有的本质区别。
以上就是所谓的动态方法。
对于静态方法,基本上所有的C/C++编译器都支持一种叫做Link的链接方法,也就是所谓的静态链接。
这样,我们所要做的就是编写包含函数、类等声明的头文件(ah、bh、...)及其对应的实现文件(a.cpp、b.cpp、...)。 .), 编译器会把它编译成静态库文件(a.lib,b.lib,...)。 在后续的代码复用过程中,我们只需要提供相应的头文件(.h)和相应的库文件(.lib),就可以使用过去的代码了。
与动态方法相比,静态方法的优势在于实现代码的隐蔽性,即C++所提倡的“接口对外,实现代码不可见”。 有利于库文件的转发。
许多人会反对说难题中最难的部分是基本概念,但事实确实如此。 高中学物理的时候,老师讲的是概念,概念要搞清楚,难的就变简单了。 如果你能分析清楚一个物理问题有几个物理过程,每个过程都遵守那个物理定律(比如动量守恒、牛二定律、能量守恒定律),那么很容易把这个过程的方程按照定律 ,N个过程一定是N个N元方程,问题就很容易解决了。 就算是高中物理竞赛题,最难的也无非是:
(一)混淆概念,导致无法分析几个物理过程,或者某个物理过程所遵循的物理规律;
(2) 有高阶方程,方程即使列出也解不出来。 后者已经属于数学的范畴,所以最难的是要把握清楚的概念;
编程也是如此。 概念清楚的话,基本没有问题(数学上会比较难,比如算法的选择,时间空间和效率的权衡,稳定性和资源的平衡)。 然而,要掌握一个清晰的概念却不是那么容易。 看看下面的例子,看看你是否有一个清晰透彻的理解。
//啊无效 foo(); //ac # "ah" //我的问题出来了:这句话到底有没有必要?
无效的 foo()
{ ; } //main.c # "ah" int main(int argc, char *argv[]) {foo(); 0; }
对于上面的代码,请回答三个问题:
. (1)ac中#“啊”这句是多余的吗?
(2)为什么在xx.c中经常看到对应的xx.h?
(3)如果不是用ac写的,编译器会自动把.h文件的内容绑定到同名的.c文件中吗? (惯于)
(以上3个问题请仔细思考10分钟,不要急着看下面的解释。:) 想的越多理解的越深。 )
好了,是时候了! 请忘掉以上3个问题以及你对这三个问题的想法,然后慢慢听我说。 正确的观念是:在C编译器看来,.h和.c都是浮云,改名为.txt或.doc也没有太大区别。 换句话说,.h和.c之间没有必然联系。 一般.h包含定义在同名.c文件中的变量、数组、函数的声明,以及需要在.c之外使用的声明。 这个语句有什么用? 只是为了便于参考需要这些声明的地方。 因为宏#“xx.h”的实际意思是删除当前行,在当前行位置原封不动的插入xx.h中的内容。 由于我要写这些函数声明的地方很多(xx.c中每一个调用函数的地方都必须在使用前声明),所以使用#"xx.h"宏可以简化很多行代码——让预处理器替换自身。 也就是说,xx.h其实只是调用了xx.c中需要写函数声明的地方(可以少写几行)。 至于这个.h文件是谁的,是.h还是.c,或者跟这个.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"#"ah"中使用,那么c.cpp如何找到b.cpp中的实现呢?
事实上,.cpp 和.h 文件名没有任何直接关系,许多编译器可以接受其他扩展名。
谭浩强先生在《C程序设计》一书中提到,编译器在进行预处理时,需要对#命令进行“文件包含处理”:将.h的全部内容复制到#
“。H”。 这也解释了为什么很多编译器不关心这个文件的后缀是什么——因为#是为了完成一个“复制和插入代码”的工作。
程序编译时不会在b.cpp文件中寻找函数实现,只有在链接时才做这个工作。 我们在b.cpp或c.cpp中使用#"ah"来实际引入相关声明,这样编译就可以通过,程序不关心它是在哪里实现的,是如何实现的。 源文件编译后,生成目标文件(.o 或.obj 文件)。 在目标文件中,这些函数和变量被视为符号。 链接的时候需要指定需要连接哪个.o或者.obj文件(这里是b.cpp生成的.o或者.obj文件),这时候链接器会去这个.o或者.obj file 查找b.cpp中实现的函数,并构建到.cpp中指定的可执行文件中。 (很重要)
在VC中,一堆情况不需要你自己写,你只需要包含所有需要的文件,VC会自动帮你写。
通常,编译器会在每个 .o 或 .obj 文件中查找所需的符号,而不是只查找某个文件或找到一个文件就不找。 因此,如果在几个不同的文件中实现了同一个功能,或者定义了同一个全局变量,链接时会提示“”。
最后,无论你是在职场成长还是在大学的入门阶段,C/C++都是一门编程语言,不仅可以增强你的思维能力,还可以为编程打下坚实的基础。 如果你想做软件开发,成为核心程序员, C/C++是更好的选择。 作者有一个千人C/C++程序员的Q群(Q船有线:C语言编程学习聚集地(默默建立))如果觉得自学C/C++语言有难度,有兴趣的小伙伴学习或者学习C/C++编程的可以进来交流。 下面给大家分享一下C/C++的学习路线图: