文件包含 与 头文件的写法

很多人对C语言中的 “文件包含”都不陌生了,文件包含处理在程序开发中会给我们的模块化程序设计带来很大的好处,通过文件包含的方法把程序中的各个功能模块联系起来是模块化程序设计中的一种非常有利的手段。

       文件包含处理是指在一个源文件中,通过文件包含命令将另一个源文件的内容全部包含在此文件中。在源文件编译时,连同被包含进来的文件一同编译,生成目标目标文件。

    很多人再初学时都会对这个很晕,怎么写文件件? 怎么包含才能避免重定义? 等等问题。。。
 
    其实这个只要了解了文件包含的基本处理方法就可以对文件包含有一个很好的理解与应用了,下来我们一起来看一下:
 
  文件包含的处理方法:

   首先大家需要清楚:

   (1) 处理时间: 文件包含也是以"#"开头来写的(#include ), 那么它就是写给预处理器来看了, 也就是说文件包含是会在编译预处理阶段进行处理的。

   (2) 处理方法: 在预处理阶段,系统自动对#include命令进行处理,具体做法是:降包含文件的内容复制到包含语句(#include )处,得到新的文件,然后再对这个新的文件进行编译。
 
   抓住这两点,那么这个东东就没有什么难的了。。。

   一般情况下文件包含分为两种包含.h文件 和 包含.c文件
1. 当然对于这两情况也都是按照上面说的方法来处理的。呵呵,这个肯定是没得说的.

2.  包含.c文件 和编译多文件程序 是不同的。
    多文件程序 是在源文件编译时把多个文件进行编译、连接在一起生成一个可执行文件。
    包含.c文件:  按照我们上边的说法则是把多个文件合并为一个文件进行编译。
接下来通过例子看一下:
(1)包含.c文件:
   
   
1: //file1: main.c
   
   
2: #include
   
   
3: #include "fun.c"
   
   
4: int main()
   
   
5: {
   
   
6: int a=5,b=19;
   
   
7: c = a;
   
   
8: sun(a,b);
   
   
9: printf( "c=%d\n",c);
   
   
10: return 0;
   
   
11: }
   
   
12: //end of file1
   1: //file2: fun.c
   2: int c=0;
   3: void sun(int a, int b)
   4: {
   5:     printf("a+b=%d\n",a+b);
   6:     c=0;
   7:     printf("c=%d\n",c);
   8: }
   9: //end of file2   
  10:   
这个例子是采用 包含.c文件 的方法实现的。
 
在编译时,直接去编译main.c文件,预处理器会先把fun.c文件中的内容复制到main.c中来,然后再对新的main.c进行编译。
编译命令:
    gcc main.c -o main
可以看到,这里并没有对fun.c进行编译,但还是生成了最终的main可执行程序。
也可以通过命令来观察一下预处理的结果:
编译命令:
   gcc -E main.c -o main.cpp
在main.cpp文件末尾可以看来下面一段代码:
   1: //main.cpp文件中
   2: 931 # 2 "main.c" 2
   3: 932 # 1 "fun.c" 1
   4: 933 //注意这里是fun.c里边的内容
   5: 934 int c=0;
   6: 935 void sun(int a, int b)
   7: 936 {
   8: 937  printf("a+b=%d\n",a+b);
   9: 938  c=0;
  10: 939  printf("c=%d\n",c);
  11: 940 } 
  12:     //这里是main函数
  13: 941 # 3 "main.c" 2
  14: 942 int main()
  15: 943 { 
  16: 944  int a=5,b=19;
  17: 945  c = a;
  18: 946  printf("c=%d\n",c);
  19: 947  sun(a,b);
  20: 948  printf("c=%d\n",c);
  21: 949  return 0;
  22: 950 }
可见,其实就是将fun.c文件中的内容添加到了main函数之前,然后对新的文件进行编译,生成最终的可执行程序。
 
(2)编译多文件程序:
同样是上边的例子,把main.c中“ #include "fun.c" ”注释掉,加上一句:“extern int c;”因为 c 变量在另外一个文件(fun.c)中定义。
   1: //file1:  main.c 
   2: #include 
   3: //#include "fun.c"  //注释掉
   4: extern int c;        //添加这一句
   5: int main()
   6: {
   7:     int a=5,b=19;
   8:     c = a;    
   9:     sun(a,b);
  10:     printf("c=%d\n",c);
  11:     return 0;
  12: }
  13: //end of file1
  14:  
  15:  
  16: //file2: fun.c
  17: int c=0;
  18: void sun(int a, int b)
  19: {
  20:     printf("a+b=%d\n",a+b);
  21:     c=0;
  22:     printf("c=%d\n",c);
  23: }
  24: //end of file2  
 
这次如果还是按照上面的方法只编译main.c的话就会出错,因为变量c和函数sun并没有在main.c中定义,所以编译时需要将fun.c一起编译:
编译命令:   
    gcc -c main.c -o main.o                 #编译main.c
    gcc -c fun.c -o fun.o                       #编译fun.c
    gcc main.o fun.o -o main              #用main.o fun.o生成main

       到这里大家应该已经理解包含.c文件和多文件程序的本质区别了~~~
好了,大家不防想想这两种方法的优缺点,这里就只写不足之处了:
1. 包含.c文件的方法: 容易产生"重定义",大家想想如果一个工程中有多个文件都同时包含了某一个件,那么这个被包含文件的内容就会被复制到多个文件中去,也就相当于每个包含该文件的文件中都定义被包含文件中的变量和函数,这样在链接时就会产生"重定义"错误。
2. 多文件分开编译的方法: 这个比较好,不容易出现"重定义"之类的问题,这也是我们最常用的一种方法,但是并不是像上面这个例子中这样直接去用,而是使用"头文件"将各个.c文件联系起来。
     上边这个例子大家会发现,在main.c中需要加上“extern int c;”这样一句声明,如果包含的文件较多?如果全局变量较多?...这个我们可以省掉吗?回答是肯定的!方法就是给它写上一个头文件。
 
       接下来看一下使用头文件的来实现这个例子的方法:
   
   
1: //file1: main.c
   
   
2: #include
   
   
3: #include "fun.h" //fun.c修改为fun.h
   
   
4: //extern int c; //这行也不要了
   
   
5: int main()
   
   
6: {
   
   
7: int a=5,b=19;
   
   
8: c = a;
   
   
9: sun(a,b);
   
   
10: printf( "c=%d\n",c);
   
   
11: return 0;
   
   
12: }
   
   
13: //end of file1
 
   
   
1:  
   
   
2: //file2: fun.c
   
   
3: #include "fun.h"
   
   
4: int c=0; //变量c的定义
   
   
5: void sun( int a, int b) //函数sun()的定义
   
   
6: {
   
   
7: printf( "a+b=%d\n",a+b);
   
   
8: c=0;
   
   
9: printf( "c=%d\n",c);
   
   
10: }
   
   
11: //end of file2
 
   
   
1: //file3: fun.h
   
   
2: extern int c; //把c声明为外部可用的
   
   
3: void sun( int a, int b); //sun()函数的声明
   
   
4: //end of file3
这样再看一下,在要用到fun.c中定义的函数或变量的文件中只要包含fun.h文件就可以了,是不是这样???呵呵,当然是了。。。

预处理时会把fun.h中的内容复制到包含它的文件中去,而复制的这些内容只是声名,不是定义,所以它被复制再多份也不会出现"重定义"的错误。。。

呵呵,对,就是这样,这就是头文件给我们再来的好处。
 
前面说了头文件的方法也是模块化程序设计中的一种非常有利的手段。

        把同一类功能写到一个.c文件中,这样可以把他们划为一个模块,另外再对应的写上一个.h文件做它的声明。这样以后再使用这个模块时只需要把这两个文件添加进工程,同时在要使用模块内函数或变量的文件中包含.h文件就可以了。
         
        举个很实际的例子,在单片机、ARM或其他嵌入式开发中,每一个平台可能本身都有多种不同的硬件模块,使用时需要去写相应的驱动程序,这样就可以把各个硬件模块的驱动程序作为一个模块(比如lcd驱动对对应lcd.c和lcd.h,IIC驱动对应I2C.c和I2C.h等),当具体使用到某个模块时,只需要在将对应的.c和.h文件添加进工程,并在文件中包含对就的.h文件即可。
 
所以关于头文件的写法个人 总结以下几点:
(1) 对应的.c文件中写变量、函数的定义
(2) 对应的.h文件中写变量、函数的声明
(3) 如果有数据类型的定义 和 宏定义 ,请写的头文件(.h)
(4) 头文件中一定加上#ifndef...#define....#endif之类的防止重包含的语句
(5) 模块的.c文件中别忘包含自己的.h文件
(6) .h头文件中不能再次包含*.c文件了,不然会出现重复定义的错误

  C文件就是C语言系列的源文件,而H文件则是C语言的头文件,即C系列中存放函数和全局变量的文件,因为C中的函数是被封装起来的,即无法看到其代码。

        子程序不要定义在*.h中。函数定义要放在*.c中,而*.h只做声明.否则多引用几次,就会发生函数重复定义的错误。*.h只做声明,编译后不产生代码。这样做目的是为了实现软件的模块化,使软件结构清晰,而且也便于别人使用你写的程序。

 

         纯粹用 C 语言语法的角度,你当然可以在*.h 中放任何东西,因为 #include 完全等价于把*.h 文件 Ctrl-C Ctrl-V 到*.c 中,*.h 中应该都是一些宏定义和变量、函数声明,告诉别人你的程序“能干什么、该怎么用”。*.c 中是所有变量和函数的定义,告诉计算机你的程序“该怎么实现”。当然,如果一个*.h 被多个*.c 包含,而且*.h 中有对象(变量或函数)的定义,就会发生重复定义的错误了,声明可以无穷多次,定义只能一次。

        一般来说,一个C文件应该是一个模块,如果你的程序仅仅有一个模块(仅仅一个C文件),就可以不用建立H文件了。否则你的模块肯定不是独立的,你的模块里面的实现要被别的模块调用。这个时候你最好生成一个头文件(H文件),在头文件里面可以声明你的那些函数是公共的。当别的模块包含你的头文件后,就可以使用你的公共声明了。

        一个C对应一个H,这样管理起来方便,比如你有一个"my.c",那么就再添加一个"my.h":

 

#ifndef   _MY_H

 

#define  _MY_H

 

extern void my(void);

 

#endif

 

其实在H文件里写函数也无所谓,只是不符合习惯而已。只要按照以上的格式写,一个H文件添加多少次都无所谓,呵~

 

 

       Come from: http://topic.csdn.net/t/20060429/15/4723725.html

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

       1.预处理阶段

       2.词法与语法分析阶段

       3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件

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

 

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

 

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

#include <stdio.h>

#include "mytest.h"

 

int main(int argc,char **argv)

{

        test = 25;

        printf("test.................%d\n",test);

}

 

mytest.h文件的内容:

int test;

 

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

        1.预处理阶段:编译器以C文件作为一个单元,首先读这个C文件,发现第一句与第二句是包含一个头文件,就会在所有搜索路径中寻找这两个文件,找到之后,就会将相应头文件中再去处理宏,变量,函数声明,嵌套的头文件包含等,检测依赖关系,进行宏替换,看是否有重复定义与声明的情况发生,最后将那些文件中所有的东东全部扫描进这个当前的C文件中,形成一个中间“C文件”。

 

        2.编译阶段,在上一步中相当于将那个头文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时就将所有这个中间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文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放在一个头文件中,想用它的C文件就只需要引用一个就OK了!!!这样岂不方便,要改某个声明的时候,只需要动一下头文件就行了。

       4.在头文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库呢?也就是如何利用你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的函数原型,这样人家才知道如何调用你写的函数,就如同你调用printf函数一样,里面的参数是怎样的??你是怎么知道的??还不是看人家的头文件中的相关声明啊!!!当然这些东东都成了C标准,就算不看人家的头文件,你一样可以知道怎么使用 。

 

例子:

//a.h

void foo();

//a.c

#include "a.h"  //我的问题出来了:这句话是要,还是不要?

void foo()

{

      return;

}

 

//main.c

#include "a.h"

int main(int argc, char *argv[])

{

      foo();

   return 0;

}

 

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

1.a.c 中的 #include "a.h" 这句话是不是多余的?为什么经常见 xx.c 里面 include 对应的 xx.h?

2.如果 a.c 中不写,那么编译器是不是会自动把 .h 文件里面的东西跟同名的 .c 文件绑定在一起?

3.第三个问题我给他改了一下:如果 a.c 中不写include<>,那么编译器是不是会自动把 .h 文件里面的东西跟同名的.c文件绑定在一起?

 

下面是一位牛人的原话:

 

    从C编译器角度看,.h和.c皆是浮云,就是改名为.txt、.doc也没有大的分别。换句话说,就是.h和.c没啥必然联系。.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。这个声明有啥用?只是让需要用这些声明的地方方便引用。因为 #include "xx.h" 这个宏其实际意思就是把当前这一行删掉,把 xx.h 中的内容原封不动的插入在当前行的位置。由于想写这些函数声明的地方非常多(每一个调用 xx.c 中函数的地方,都要在使用前声明一下子),所以用 #include "xx.h" 这个宏就简化了许多行代码——让预处理器自己替换好了。也就是说,xx.h 其实只是让需要写 xx.c 中函数声明的地方调用(可以少写几行字),至于 include 这个 .h 文件是谁,是 .h 还是 .c,还是与这个 .h 同名的 .c,都没有任何必然关系。

       这样你可能会说:啊?那我平时只想调用 xx.c 中的某个函数,却 include了 xx.h 文件,岂不是宏替换后出现了很多无用的声明?没错,确实引入了很多垃圾 ,但是它却省了你不少笔墨,并且整个版面也看起来清爽的多。鱼与熊掌不可得兼,就是这个道理。反正多些声明(.h一般只用来放声明,而放不定义,参见拙著“过马路,左右看”)也无害处,又不会影响编译,何乐而不为呢?

翻回头再看上面的3个问题,很好解答了吧?

 

它的解答如下:

1.不一定。这个例子中显然是多余的。但是如果.c中的函数也需要调用同个.c中的其它函数,那么这个.c往往会include同名的.h,这样就不需要为声明和调用顺序而发愁了(C语言要求使用之前必须声明,而include同名.h一般会放在.c的开头)。有很多工程甚至把这种写法约定为代码规范,以规范出清晰的代码来。

2.答:1中已经回答过了。

3.答:不会。问这个问题的人绝对是概念不清,要不就是想混水摸鱼。非常讨厌的是中国的很多考试出的都是这种烂题,生怕别人有个清楚的概念了,绝对要把考生搞晕。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值