Chip Studio

常用链接

统计

最新评论

关于C/C++语言中头文件的使用的一些看法

一、  只有一个文件的情况

先来看一下比较简单的情形,也就是只有一个文件的时候,一个程序是什么样子的。

//main.c

#include <stdio.h>

 

int main(int argc, char** args)

{

    printf("Hello\n") ;

    return 0 ;

}

这个时候程序一目了然,我们很容易就可以看出它说了什么。

 

二、  多个源代码文件的情况

但是,随着我们要编写的程序的规模不断扩大,我们不得不把一个源代码文件拆分开,把具有一定功能的某些方法放到其它单独的源码文件中。比如像下面这样:

//main.c

#include <stdio.h>

 

int main(int argc, char** args)

{

    sayhello() ;

    return 0 ;

}

 

//sayhello.c

#include <stdio.h>

int sayhello()

{

    printf("Hello\n") ;

}

把功能放在了sayhello.c文件中,而main.c只放主函数的代码。这样看起来更加的清晰明快。虽然形式上分成多个文件,但编译器在编译的时候会自动把它们连在一起,也就是说它们还是相当于在一个大文件中写代码。但是如果我们启图分别编译这两个文件,然后再链接成一个可执行程序的时候,就会发生错误。原因在于main.c中使用了一个函数sayhello,这时编译器并不知道sayhello是什么,因为相对于main.c来说,它并不存在sayhello的定义和实现。所以,我们必须要在main.c中加入sayhello的声明(只要声明就够了,不必再实现一次)。方法是加一句int sayhello() ;但问题是,当我们的工程越来越大的时候,我们总不能引用一个函数就写一下它的都声明吧?

三、  引入头文件

这时最好的解决办法就是引用头文件。就是编写一个与sayhello.c同名的文件sayhello.h,用于定义常量、结构,声明函数等。具体的做法如下:

//main.c

#include <stdio.h>

#include "sayhello.h"

 

int main(int argc, char** args)

{

    sayhello() ;

    return 0 ;

}

 

//sayhello.c

#include <stdio.h>

#include "sayhello.h"

int sayhello()

{

    printf("Hello\n") ;

}

 

//sayhello.h

int sayhello() ;

 

四、  说说include

对于头文件,我们应仅把它看作是一个文本文件,它跟程序的代码文件(即扩展名为.c的文件)并不一样。编译器在编译的过程中,只会处理代码文件,而不会去管其它的头文件。只有当我们在头文件中使用#include的时候,相应的头文件才会被包含进来。编译器只是在编译前把#include所在的位置换成了相应头文件中的内容罢了。

使用<>括起来的是系统的默认库文件,也就是说不用咱们自己去找这个文件所在的位置,只写一个名字,编译器就自动找到库目录中的文件了。而””括起来的正好相反,大多是我们自己编的代码或引用的非标准C的库文件,它要求给出文件所在的绝对地址或相对地址。比如说,如果你的库目录设成/usr/share/include,那么下面的写法是等价的:

#include <stdio.h> == #include “/usr/share/include/stdio.h”

 

五、  谈谈头文件具体的使用

道理都懂了,那么自己写程序时,我的头文件到底应该怎么写呢?其实,头文件的写法很随意,很多人都有自己的使用习惯。但是我自己的看法是,尽量模仿标准C的库。现在就来研究一下吧。

比如我们平时使用printf时,我们都要包括一个头文件,即stdio.h。它的特点是我在哪个代码文件用到了这个库中的函数,我就在哪个代码文件中包括它的头文件;包含它后,我的代码中不应该引入错误,引用的库函数不应该因为代码文件中多引用了或少引用了一些其它的头文件而出错。

为了达到这个目标,我的做法是:每写一个代码文件,就写一个对应的头文件;把所有的声明、定义、结构体、常量、宏放在头文件中,而代码实现绝对不放在头文件中;对头文件的抱含也放到头文件中,代码文件中不含include宏。

下面看一些反例:

反例1:

//types.h

typedef int status ;

 

//sayhello.h

status sayhello() ;

 

//sayhello.c

#include <stdio.h>

#include "types.h"

#include "sayhello.h"

 

status sayhello()

{

    printf("Hello!\n") ;

    return 0 ;

}

 

//main.c

#include "sayhello.h"

 

int main(int argc, char** argv)

{

    sayhello() ;

    return 0 ;

}

sayhello的定义中,出现了一个自定义类型status,它的声明包括在types.h文件中。放对它的引用放在了sayhello.c中,这样单独编译sayhello.c没有任何问题。可是当编译到main.c的时候,就出现问题了,编译报错:找不到status的声明。这是因为在main.c中只抱括了sayhello.h,而它的声明又需要types.h。所以,它出现了由于少引用types.h而发生的错误。所以,我强调把所有的include都放到头文件中去。如果这样写则不会出问题。

//sayhello.h

#include <stdio.h>

#include "types.h"

 

status sayhello() ;

 

//sayhello.c

#include "sayhello.h"

 

status sayhello()

{

    printf("Hello!\n") ;

    return 0 ;

}

//main.c

#include "sayhello.h"

 

int main(int argc, char** argv)

{

    sayhello() ;

    return 0 ;

}

这样就符合了前面提到的原则。不过,我还可以做如下的改动:

//main.c

#include "sayhello.h"

#include "types.h"

 

int main(int argc, char** argv)

{

    status s = sayhello() ;

    return 0 ;

}

在主程序中声明了status类型的变量s。根据上面的原则,哪里引用了它,哪里就包括它的头文件,所以我们包括了types.h头文件。有人会说:“没有types.h,也一样不会出错啊,在sayhello.h中不是引用过types.h吗?”这样做真的是多此一举吗?当然不是,我觉得它是相当有意义的。第一,它维护了我们自己定下的原则。保持一个不变的代码习惯是很有好处的。第二,由于我们保证了头文件中不加入实现性质的代码,只写些声明类的代码,它们在编译时只是起来语法制导的作用,并不会被成为目标程序的一部分,所以这样写并不会造成浪费。这也是提倡头文件中不要夹杂代码的一个原因。

如果真的不想把头文件编译多次的话,还有一个办法,如下:

#ifndef _HEADER_FILE_

#define _HEADER_FILE_

//声明部分

//...........

#endif

这样写可以保证编译器只编译一次,其中的_HEADER_FILE_自己定义的头文件的唯一标识,只要别跟常量定义和别的头文件冲突,您喜欢叫它什么就叫它什么吧。^^

 

六、  总结

最后总结一下吧。如果当你在编写自己庞大的代码文件群的时候,遇到了一些犹豫,就想想上面的原则和应用。当因为头文件编译出错的时候,考虑一下是否自己有哪些动作违反了上面的原则。只要经常思考,每个人都会总结是适合自己的使用习惯,尽量减少在这些程序员看来无关紧要的事情上出错的机会。以上只是本人自己使用习惯的一次总结,不代表任何规范和标准,欢迎善意的批评指正.^_^

posted on 2008-02-26 14:04 MyChip 阅读(2907) 评论(0)  编辑 收藏 引用 所属分类: C/C++/CLI