当前位置: 首页 > 编程笔记 >

详解C 语言项目中.h文件和.c文件的关系

毛淳
2023-03-14
本文向大家介绍详解C 语言项目中.h文件和.c文件的关系,包括了详解C 语言项目中.h文件和.c文件的关系的使用技巧和注意事项,需要的朋友参考一下

详解C 语言项目中.h文件和.c文件的关系

在编译器只认识.c(.cpp))文件,而不知道.h是何物的年代,那时的人们写了很多的.c(.cpp)文件,渐渐地,人们发现在很多.c(.cpp)文件中的声明语句就是相同的,但他们却不得不一个字一个字地重复地将这些内容敲入每个.c(.cpp)文件。但更为恐怖的是,当其中一个声明有变更时,就需要检查所有的.c(.cpp)文件。

    于是人们将重复的部分提取出来,放在一个新文件里,然后在需要的.c(.cpp)文件中敲入#include XXXX这样的语句。这样即使某个声明发生了变更,也再不需要到处寻找与修改了。因为这个新文件,经常被放在.c(.cpp)文件的头部,所以就给它起名叫做“头文件”,扩展名是.h。

    在我们语言的初学阶段,往往我们的程序只有一个.c的文件或这很少的几个,这时我们就很少遇到头文件组织这个头疼的问题,随着我们程序的增加,代码 量到了几千行甚至几万行,文件数也越来越多。这时这些文件的组织就成了一个问题,其实说白了这些文件的组织问题从理论上来说是软件工程中的模块设计等等的问题。

    头文件的作用的简短描述:

(1)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。
(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

    比方说 我在aaa.h里定义了一个函数的声明,然后我在aaa.h的同一个目录下建立aaa.c , aaa.c里定义了这个函数的实现,然后是在main函数所在.c文件里#include这个aaa.h  然后我就可以使用这个函数了。 main在运行时就会找到这个定义了这个函数的aaa.c文件。这是因为:main函数为标准C/C++的程序入口,编译器会先找到该函数所在的文件。假定编译程序编译myproj.c(其中含main())时,发现它include了mylib.h(其中声明了函数void test()),那么此时编译器将按照事先设定的路径(Include路径列表及代码文件所在的路径)查找与之同名的实现文件(扩展名为.cpp或.c,此例中为mylib.c),如果找到该文件,并在其中找到该函数(此例中为void test())的实现代码,则继续编译;如果在指定目录找不到实现文件,或者在该文件及后续的各include文件中未找到实现代码,则返回一个编译错误.其实include的过程完全可以“看成”是一个文件拼接的过程,将声明和实现分别写在头文件及C文件中,或者将二者同时写在头文件中,理论上没有本质的区别。

    理论上来说C文件与头文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在头文件中写函数体,只要在任何一个C文件包含此头文件就可以将这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不在任何C文件中包含此头文件的话,这段代码就形同虚设),你可以在C文件中进行函数声明,变量声明,结构体声明,这也不成问题!!!那为何一定要分成头文件与C文件呢?又为何一般都在头件中进行函数,变量声明,宏声明,结构体声明呢?而在C文件中去进行变量定义,函数实现呢??

   要理解C文件与头文件有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:

1.预处理阶段
2.词法与语法分析阶段
3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件
4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件,在PC上的程序开发,一般都有一个main函数,这是各个编译器的约定。为了生成一个最终的可执行文件,就需要一些目标文件,也就是需要C文件,而这些C文件中又需要一个main函数作为可执行程序的入口。 

    简单些说就是C语言的编译分为预处理、编译、汇编、链接(test.c test.h => test.i => test.s => test.o => test)四个大的阶段。c文件中的#include宏处理,会在预处理的阶段将c中引用的h文件的内容全部写到c文件中,最后生成.i中间文件,这时h 文件中的内容就相当于被写道c文件中。这也为代码的复用提供了渠道,很多的c文件可以去引用同一个h文件,这样这个h文件就会被放到多个c文件中被编译多 次,这也是h文件中不能放定义只能放声明的原因,放定义时被编译多次,在程序链接的时候(系统中定义了多个int a;强符号定义)会出现错误, 声明就不一样,声明表示对定义的扩展,最终都会终结到一个定义上,所以不会出现link时重复定义的错误。 

编程中我们在h文件中肯定都用过一下的格式

#ifndef  XXX_H
#define  XXX_H
 //……
#endif

呵呵,那他到底有什么用呢,在h文件互相引用时,消除重复定义。当然宏定义是在预处理阶段发挥作用的,编译方后的过程是没有宏的影子的。

A.h
int a();
 
B.h
#include "A.h"
 
C.h
#include "A.h"
 
D.h
#include "A.h"
#include "B.h"

上面的D.h文件中就会重复出现两个int a();的声明阿,这样就有点重复了,这时条件编译宏就派上了用场

A.h
#ifndef A_H
#define A_H
int a();
#endif

这样就不会重复定义了。

 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

 类似资料:
  • 主要内容:fopen() 函数的返回值,fopen() 函数的打开方式,关闭文件,实例演示在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。 打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。 标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的

  • 本文向大家介绍C语言 文件的打开与关闭详解及示例代码,包括了C语言 文件的打开与关闭详解及示例代码的使用技巧和注意事项,需要的朋友参考一下 在C语言中,文件操作都是由库函数来完成的,这节介绍文件的打开和关闭。 文件的打开(fopen函数) fopen() 函数用来打开一个文件,它的原型为: FILE *fopen(char *filename, char *mode); filename为文件名(

  • 主要内容:文件流我们对文件的概念已经非常熟悉了,比如常见的 Word 文档、txt 文件、源文件等。文件是数据源的一种,最主要的作用是保存数据。 在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。例如: 通常把显示器称为标准输出文件,printf 就是向这个文件输出数据; 通常把键盘称为标准输入文件,scanf 就是从这个文件读取数

  • 《 C++ open打开文件》一节中,详细介绍了文件流对象如何调用 open() 成员方法打开指定文件。相对应地,文件流对象还可以主动关闭先前打开的文件,即调用 close() 成员方法。 我们知道,调用 open() 方法打开文件,是文件流对象和文件之间建立关联的过程。那么,调用 close() 方法关闭已打开的文件,就可以理解为是切断文件流对象和文件之间的关联。注意,close() 方法的功能

  • 本文向大家介绍C语言按关键字搜索文件夹中文件的方法,包括了C语言按关键字搜索文件夹中文件的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C语言按关键字搜索文件夹中文件的方法。分享给大家供大家参考。具体实现方法如下: 方法1: 方法2: 希望本文所述对大家的C语言程序设计有所帮助。

  • 本文向大家介绍C语言中const和C++中的const 区别详解,包括了C语言中const和C++中的const 区别详解的使用技巧和注意事项,需要的朋友参考一下 C语言中const和C++中的const 区别详解 C++的const和C语言的#define都可以用来定义常量,二者是有区别的,const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有