C语言中头文件相互包含问题

时间:2021-08-07 16:46:01

    .h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。

 

1)h文件作用 

 

方便开发:包含一些文件需要的共同的常量,结构,类型定义,函数,变量申明; 

 

提供接口:对一个软件包来说可以提供一个给外界的接口(例如: stdio.h)

 

 

 

2)h文件里应该有什么 

 

    常量,结构,类型定义,函数,变量申明。 

 

 

3)h文件不应该有什么 

 

    变量定义函数定义。

 

 

4)extern问题 

 

    对于变量需要extern; 

 

    对于函数不需要因为函数的缺省状态是extern.如果一个函数要改变为只在文件内可见,static

 

5)include包含问题 

 

    虽然申明和类型定义可以重复,不过推荐使用条件编译。

 

 

#ifndef _FILENAME_H,     //如果未定义_filename.h,则定义

#define _FILENAME_H

……

#endif 

 

 

 

6)应该在那儿包含h文件 

 

    在需要的地方.比如某个提供接口的h文件仅仅被1.c文件需要,那么就在1.c文件里包含。                 

 

编写的程序一般会有.H文件和相对应的.C文件,.H文件是声明所用,.C文件是其函数实现部分。在调用时只要包含.H文件即可,我们没有听说过#include "delay.c"这类的程序,同时也不提倡使用这个形式。

 

delay.h文件中:                //对调用的函数声明

 

#ifndef __DELAY_H__

#define __DELAY_H__

extern void Delayms(unsigned int n);     //包含在保护宏中

#endif

 

 

 

delay.c文件中:                 //函数实现部分


#include <delay.h>                             //for crystal 11.0592M    延迟函数

void Delayms(unsigned int n)

{

       unsigned int i,j; 

       for(j=n;j>0;j--)

       for(i=112;i>0;i--);

}

 

 

在主程序main.c


#include <delay.h>                //在主程序包含.h文件,不能包含.c文件

通常一个C程序工程按功能可以分成多个模块, 一个模块通常由两个文档组成一个头文件 *.h, 对模块中的数据结构和函数原型进行描述;另一个为C文件*.C , 对数据实例或对象进行定义,以及函数算法的具体实现,如I2C.C, SPI.C, DAC.C, DISPLAY.C 等,为了文件的调用,我们要为每个模块定义一个头文件,以I2C.C 来说,定义I2C.H

 

#ifndef GRAPHICS_H                    /*防止graphics.h被重复引用*/ 

#define GRAPHICS_H   

#include   <math.h >                    /*引用标准库的头文件*/ 

…                                        

#include   myheader.h”               /* 引用非标准库的头文件*/ 

…                                           

void   Function1();                      /*全局函数声明*/ 

…                                        

class   Box                                    /*类结构声明*/ 

… 

 

}; 

#endif 

 

******************************************************************************************************************************************************************************************************************

 

    模块化的程序是黑盒,只向外提供接口(全局变量、外部函数),而不需要让调用者了解其中过程。尽可能地少定义接口有利于保持模块的独立性(不需要让使用者知道的内部函数与静态全局变量不需要在H文件中给出以避免使用者疑惑)在需要调用此模块的文件中写入include语句。一个好的工程,H文件的组织是很清晰的,只看H文件就能够写主程序调用相应的C模块。

   

    头文件的格式如下(I2C.H为例)

 

********************************************************************

 

 

 

 #ifndef   I2C_H                        /*是否没有定义过 "I2C_H防止重定义*/

 #define   I2C_H                    /*定义"I2C_H" */

  ..........

 bit  SetSDA ( bit  Up_Down );

 bit   SetSCL ( bit  Up_Down);

#endif        

 

 

**********************************************************************

 

 

 

           I2C.C格式如下:

 

**********************************************************************

 

#include  < stdio.h >

#include  "I2C.h"

void SendByte ( uchar c );                        /*内部函数在.H 头文件中不描述*/

bit  SetSDA ( bit  Up_Down )     { .......... };

bit   SetSCL ( bit  Up_Down)     { .......... };

 

**********************************************************************

 


另外一种写法:

 

=============================

  #ifndef   I2C_H

  #define   I2C_H

   ..........

  exten  bit  SetSDA ( bit  Up_Down );

  exten  bit   SetSCL ( bit  Up_Down);

#endif   

 

 

=================================================

 

 I2C.C格式如下:

 

 

=================================================

 

 

 

#include  < stdio.h >

void SendByte ( uchar c );                           /*内部函数在.H 头文件中不声明*/    //  在外部是否可一点用????

bit  SetSDA ( bit  Up_Down )     { .......... };   //同文件声明函数的实现

bit  SetSCL ( bit  Up_Down)      { .......... };

 

 

 

=================================================

 

 

举个例子,顺便分析一下ifndef/define/endif: 

 

假设你的工程里面有4个文件,分别是a.cpp, b.h, c.h, d.h。 

 

a.cpp的头部是: 

 

     #include   "b.h" 

 

     #include   "c.h" 

 

 

 

b.hc.h的头部都是

 

     #include   "d.h" 

 


    而d.h里面有class D的定义。 

 

    这样一来, 编译器编译a.cpp的时候,先根据#include "b.h"去编译b.h这个问题,再根据b.h里面的#include "d.h",去编译d.h的这个文件,这样就把d.h里面的class D编译了; 

 

    然后再根据a.cpp的第二句#include "c.h",去编译c.h,最终还是会找到的d.h里面的class D,但是class D之前已经编译过了,所以就会报重定义错误。 

 

 

    加上ifndef/define/endif,就可以防止这种重定义错误。在预编译的过程中,执行到include "C.h"时会因为在上一句的时候已经定义了class D这个宏,所以此时的ifndef条件不满足,起到了防止重复引用头文件的效果。

 

 

     #undef只是撤消掉掉原来定义的宏,但是不会取消掉你已经用这个宏定义的变量 

 

     #define   X   extern 

 

     x   int   a; 

 

     #undef   X 

 

 

 

     你仍然可以使用这个a,但不能用X了,当然你再定义X成什么就随便了 

 

你也可以再定义成 

 

     #define   X   extern 

 

               x   int   a; 

 

     #undef    X 

 

     #define   X   int 

 

               X   b; 

 

     #undef    X

 

******************************************************************************************************************************************************************************************************************

 

模块划分的""是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。C语言作为一种结构化的程序设计语言,在模块的划分上主要依据功能(依功能进行划分在面向对象设计中成为一个错误,牛顿定律遇到了相对论),C语言模块化程序设计需理解如下概念:

 

 

    (1) 模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明;

 

    (2) 某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern关键字声明

 

    (3) 模块内的函数和全局变量需.c文件开头冠static关键字声明;

 

    (4) 永远不要在.h文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。如:

 

/*module1.h*/

int a = 5; /* 在模块1.h文件中定义int a */

 

/*module1 .c*/

#include "module1.h" /* 在模块1中包含模块1.h文件 */

 

/*module2 .c*/

#include "module1.h" /* 在模块2中包含模块1.h文件 */

 

/*module3 .c*/

#include "module1.h" /* 在模块3中包含模块1.h文件 */

 

 

 

  以上程序的结果是在模块123中都定义了整型变量aa在不同的模块中对应不同的地址单元,这个世界上从来不需要这样的程序。正确的做法是:

 

 

/*module1.h*/

extern int a;                    /* 在模块1.h文件中声明int a */

 

/*module1 .c*/

#include "module1.h"       /* 在模块1中包含模块1.h文件 */

int a = 5;                        /* 在模块1.c文件中定义int a */

 

/*module2 .c*/

#include "module1.h"       /* 在模块2中包含模块1.h文件 */

 

/*module3 .c*/

#include "module1.h"      /* 在模块3中包含模块1.h文件 */

 


这样如果模块123操作a的话,对应的是同一片内存单元

 

******************************************************************************************************************************************************************************************************************

 

 


程序设计也是如此,如果概念很清晰,那基本上没什么难题(会难在数学上,比如算法的选择、时间空间与效率的取舍、稳定与资源的平衡上)。但是,要掌握清晰的概念也没那么容易。比如下面这个例子,看看你有没有很清晰透彻的认识。  


//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" 这句话是不是多余的? 

2.为什么经常见 xx.c 里面 include 对应的 xx.h? 

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

(请针对上面3道题仔细考虑10分钟,莫要着急看下面的解释。:) 考虑的越多,下面理解的就越深。)

 

  好了,时间到!请忘掉上面的3道题,以及对这三道题引发出的你的想法,然后再听我慢慢道来。正确的概念是:从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.答:不会。问这个问题的人绝对是概念不清,