简而言之,要了解C 文件和头文件(.h) 之间的区别,首先需要了解编译器的工作过程。一般来说,编译器会执行以下过程:
1. 预处理阶段
2.词汇语法分析阶段
3. 编译阶段首先将其编译为纯汇编语句,然后将其汇编为CPU相关的二进制代码,产生各个目标文件(.obj文件)。
4、连接阶段将每个代码段的绝对地址放置在每个目标文件中,以生成与特定平台相关的可执行文件。 当然,你也可以使用objcopy来生成纯二进制代码。格式信息将被删除。 (生成.exe文件)
编译器单独编译C文件。换句话说,如果你的项目中没有C文件,连接器就会以目标文件为单位,一一进行编译。在PC上的程序开发中,通常有一个主函数,这是每个编译器的约定,并且在使用服务器脚本时,多个目标文件重新排列函数和变量。无需使用main函数作为程序入口。
(主.c文件目标文件可执行文件)
考虑到这些基本知识,为了生成最终的可执行文件,我们需要几个目标文件,即C文件,并且这些C文件包括一个主文件,该主文件作为可执行程序的入口点。我们从一个C文件开始,假设这个C文件有以下内容:
#include stdio.h
#include 'mytest.h'
int main(int argc,char **argv)
{
测试=25;
printf('测试.%d\n',测试);
}
mytest.h头文件内容如下:
内部测试;
接下来,用这个例子来说明编译器是如何工作的。
1、在预处理阶段,编译器首先读取C文件,发现第一条和第二条语句包含头文件,然后在所有搜索路径中搜索这两个文件。它们在相应的头文件中处理宏、变量、函数声明、嵌套头文件包含等,检测依赖关系,替换宏,并检查重复的定义和声明,最后将这些文件添加到所有内容中。扫描当前C文件,形成中间“C文件”。
2、编译阶段相当于上一步将头文件中的测试变量扫描到中间C文件中。此时,test变量就成为该文件内所有中间C文件中的全局变量。该文件为文件中的所有变量和函数分配空间,将每个函数编译为二进制代码,按照特定的目标文件格式生成目标文件,并按照该格式将每个全局变量和函数符号写入目标文件中并编译。他们。根据特定标准组织成目标文件的二进制代码
3、连接阶段,根据一些参数连接上一步生成的各个目标文件,生成最终的可执行文件。主要任务是重新排列每个目标文件中的函数、变量等。重新排列每个目标文件中的函数和变量二进制代码根据特定规范组合成单个文件。让我们回到C 文件和头文件中要写入的内容。理论上:无论您在C 文件和头文件中编写什么内容,C 语言都支持。例如,如果你把函数体写在一个头文件中,只要这个头文件包含在C语言中就可以了。您可以将文件中包含的函数编译为目标文件的一部分(编译基于C文件;如果包含此头文件,则此代码无用)。这不是问题。
那么为什么我们需要将其拆分为头文件和C文件呢?另外,为什么函数、变量声明、宏声明和结构体声明一般都在头文件中进行?如果在C文件中定义变量,函数的实现会发生什么?原因如下:
1、如果一个函数体是在一个头文件中实现的,并且在多个C文件中引用了它,并且同时编译了多个C文件,则生成的目标文件会附加到可执行文件和每个引用生成的目标文件中C 文件的头文件中包含该函数的代码副本。如果该函数没有定义为本地函数,连接时会检测到多个相同的函数,并报错。
2. 如果在头文件中定义了全局变量并为其分配了初始值,则引用该头文件的多个C 文件中也会存在相同变量名的副本。这个变量很重要。最终,编译器会将这个变量放置在DATA段中,但是DATA段内会存在多个相同的变量。也就是说,这些变量不能合并为一个变量。在此变量中使用空格而不是多个空格。假设头文件中没有为该变量分配初始值,编译器将其放置如下:
对于BSS段,连接器只为BSS段中的同名变量分配一个存储区域。
3. 如果在一个C 文件中声明了宏、结构体、函数等,并且想要引用另一个C 文件中相应的宏或结构体,则如果更改C 文件,则必须再次执行相同的操作。如果您忘记更改其他C 文件中的声明,您将遇到大问题,以至于您无法想象程序的逻辑。如果您想使用该C 文件,这就是您所需要的。就用一个作为参考吧!如果您想更改特定语句时只需更改头文件,岂不是很好?
4、在头文件中声明结构体、函数等。如果您需要将您的代码封装在一个库中并让其他人使用它,但您又不想发布您的源代码,那么如何让您的库可供其他人使用?也就是如何使用库内的各种功能。 • 一种方法是让源代码可供其他人自由使用。另一种选择是提供头文件,以便其他人可以从头文件中看到函数原型。这将告诉您如何调用您创建的函数。就像调用printf函数一样,内部参数是什么呢?你怎么知道的?不要看别人头文件中的相关语句。当然,这些现在都是C标准了。即使不阅读其他人的头文件,您也可以学习如何使用它。
C 语言中.c和.h文件的困惑
基本没有区别。 这很常见。h 文件是包含函数声明、宏定义、结构体定义等的头文件。
.c 文件是包含函数实现、变量定义等的程序文件。另外,无论后缀是什么,编译器都会默认对具有某些后缀的文件执行某些操作。您可以强制编译器将任何后缀的文件编译为c 文件。
一个好的编程风格是将它们编写在两个单独的文件中。
另外,例如,在aaa.h中定义函数声明,在与aaa.h相同的目录中创建aaa.c,在aaa.c中定义该函数的实现,并将main函数放在其中。 #将此aaa.h 包含在您的.c 文件中以便能够使用此函数。当main 运行时,它会找到定义该函数的aaa.c 文件。
翻译是:
main函数是标准C/C++程序的入口点,编译器首先找到该函数所在的文件。
如果编译器编译myproj.c(其中包含main())并发现其中包含mylib.h(其中声明了函数void test()包含路径列表,即代码文件所在的路径),则寻找实现具有相同名称的文件(扩展名.cpp 或.c,在本例中为mylib.c),如果找到该文件,则查找该函数(在本例中为void test())并继续编译。如果在指定的目录中找不到实现文件,或者在该文件和后续的包含文件中找不到实现代码,则返回编译错误,并且包含进程完全终止。细“看”就是合并文件的过程。声明和实现分别写入头文件和C 文件中。或者两者同时写入头文件中。理论上来说,没有本质区别。
以上就是所谓的动态法。
关于静态方法,基本上所有的C/C++编译器都支持一种称为Static Link的链接方式,即所谓的静态链接。
这样,你只需要编写包含函数、类等声明的头文件(a.h、b.h、)及其相应的实现文件(a.cpp、b.cpp、)。 ),编译器将其编译成静态库文件(a.lib,b.lib,)。后续的代码复用操作只需提供相应的头文件(.h)和相应的库文件(.lib)即可使用您过去的代码。
静态方法相对于动态方法的优点是实现代码隐藏,或者如C++中所提倡的,“接口是外部的,实现代码是不可见的”。帮助传输库文件。
许多人可能不同意这个难题最困难的部分是基本概念,但事实确实如此。当我在高中学习物理时,我的老师专注于概念。必须理解概念。因此,困难的问题变成了简单的问题。一个物理问题涉及多个物理过程,如果你能清楚地分析每个过程遵循哪些物理定律(例如动量守恒、Cow II定律、能量守恒定律等),那么很容易列出这个过程的方程。根据定律,N个过程必须有N个元方程,这个问题很容易解决。这是高中物理竞赛中最难的部分。
(1)概念混乱导致无法分析多个物理过程或遵循特定物理过程的物理定律。
(2)存在即使枚举也无法求解的高阶方程。由于后者已经是数学中的一个范畴,因此最困难的部分是获得一个清晰的概念。
编程也是一样,只要概念清楚,基本上不难(数学上比较难,比如算法的选择,时间、空间、效率的权衡,稳定与稳定的平衡)。资源)。然而,掌握一个清晰的概念并不那么容易。请看下面的例子,检查您是否理解清楚、完整。
//a.h void foo(); //a.c #include 'a.h' //我的问题是:这个语句有必要吗?
无效foo()
{ return; } //main.c #include 'a.h' int main(int argc, char *argv[]) {foo();
关于上述代码,回答以下三个问题。
(1) #include 'a.h' in a.c 语句是否多余?
(2) 为什么经常看到xx.h对应xx.c include?
(3) 如果.h文件不是用a.c编写的,编译器是否会自动将.h文件的内容绑定到同名的.c文件上?
(思考上面的三个问题10分钟。不要急于完成下面的说明。)你想得越多,你的理解就会越好。 )
好了,时间到了!忘记上面的三个问题以及它们引起的你的想法,慢慢地听。正确的概念是,从C编译器的角度来看,h和.c只是浮云,将它们重命名为.txt或.doc并没有太大区别。也就是说,h和.c之间没有必然联系。h 通常放置在同名的.c 文件中定义的变量、数组和函数的声明,以及需要由.c 外部使用的声明。这个声明有什么用呢?它只是让需要这些声明的地方更容易引用。因为#include 'xx.h'宏的实际含义是删除当前行,并将xx.h的内容原封不动地插入到当前行中。因为有很多地方你可能想要编写这些函数声明(在xx.c 中调用函数的任何地方都必须先声明才能使用),所以使用#include 'xx.h' 宏将为你节省大量的时间。代码行被简化。请自行更换预处理器。换句话说, xx.h 实际上只是在xx.c 中应该有函数声明的地方被调用(即使它少了几行)。谁包含了这个.h 文件?或.c 或者与此相关?同名的h和.c没有必然关系。
那么你可能会说:啊?那么,如果平时只是想调用xx.c中的某个特定函数,但是如果包含了xx.h文件,宏替换后岂不是会得到很多无用的语句?当然,它会产生很多垃圾,但它节省了大量笔和墨水的使用,而且整个布局看起来更干净。鱼与熊掌不可兼得,这是事实。无论如何,多一些声明(.h通常只用于声明,不用于定义;参见我的书《过马路时向两边看》)并没有什么坏处;它对编译也没有影响。
回顾并回答以上三个问题。答:不一定。这个例子显然是多余的。但是,如果.c中的函数还需要调用同一个.c中的其他函数,则该.c通常包含同名的.h,因此您不必担心声明和调用顺序(C. language) 必须在声明之前使用,包含名称.h 通常放在.c) 的开头。很多项目甚至都同意这种编写方式作为代码规范,以标准化清晰的代码。
答:1.已经回答了。
答:不。问这个问题的人要么没有明确的想法,要么只是想大海捞针。真正令人不安的是,中国的许多考试都有非常糟糕的问题。如果别人有明确的想法,考生肯定会感到困惑。
理解语法和概念说起来容易做起来难。这里有三个技巧。与其发呆地工作,不如花时间多思考、多阅读。
想读书就读好书,想问人就问强者。坏书和坏人会给你错误的概念并误导你。
这是一个很好的教训,努力工作可以弥补你的缺点。
(1)通过头文件调用库函数。很多情况下,只要向用户提供头文件和二进制库,就不方便(或不允许)向用户公开源代码。用户只需根据头文件中的接口声明调用库函数,而不用担心接口是如何实现的。编译器从库中提取相应的代码。
(2)头文件可以增强类型安全检查。如果接口的实现或使用方式与其在头文件中的声明不一致,则编译器会标记错误。这个简单的规则大大减轻了程序员调试和纠错的负担。
头文件用于存储函数原型。
头文件和源文件有什么关系?
问题实际上是头文件“a.h”声明了一组函数(只有函数原型,没有函数实现),而这些函数已知在“b.cpp”中实现,这就是我所说的。cpp' 使用在'a.h' 中声明并在'b.cpp' 中实现的函数。通常#include 'a.h' 与'c.cpp' 一起使用。那么对于c.cpp我们该怎么办呢?在b.cpp中找到实现。
事实上,cpp 和.h 文件名之间没有直接关系,许多编译器可以接受其他扩展名。
谭浩强老师的书《C程序设计》中指出,编译器预处理时应该使用#include命令来“包含文件”。将headfile.h 的全部内容复制到#include。
“headfile.h”的位置。这也解释了为什么很多编译器不关心这个文件后缀。 #include预处理完成了“复制和插入代码”的工作。
程序编译时不会搜索b.cpp文件中的函数实现。这仅在链接时完成。在b.cpp或c.cpp中使用#include 'a.h'实际上引入了相关声明,因此程序并不关心实现在哪里以及如何实现。编译源文件时,会生成目标文件(.o 或.obj 文件)。在目标文件中,这些函数和变量被视为符号。链接时,您需要在makefile 中指定应连接哪个.o 或.obj 文件(在本例中为b.cpp 生成的.o 或.obj 文件)。 o 或.obj 文件。找到b.cpp中实现的函数并将它们构建到makefile中指定的可执行文件中。 (很重要)
使用VC,在某些情况下您不需要自己创建makefile;您只需将所需的文件包含在项目中,VC 就会为您创建一个。
编译器通常在每个.o 或.obj 文件中搜索所需的符号,而不是在特定文件中搜索符号或如果找到则不搜索。因此,如果在几个不同的文件中实现了相同的函数,或者定义了相同的全局变量,则会在链接时提示您“重新定义”它。
最后,无论你是在职场成长还是进入大学,C/C++不仅可以增强你的思维能力,而且如果你想做软件开发并成为核心的话,它还可以为你打下坚实的编程基础。它是一门编程语言这可以让你如果您是程序员,我们建议学习C/C++。我们有一个Q群(Q Shipsuo:C语言编程学习群(无言创办)),有数千名C/C++程序员。您可以自己使用C++语言,或者有兴趣学习或了解C/C++编程的小伙伴也可以加入交流。分享您的C/C++ 学习路线图。
版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。