文件包含

文件包含意义

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

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

如何才能避免重定义?

文件包含的两个处理阶段

  • 处理时间:文件包含也是以"#"开头来写的(#include ), 那么它就是写给预处理器来看了, 也就是说文件包含是会在编译预处理阶段进行处理的;
  • 处理方法:在预处理阶段,系统自动对#include命令进行处理,具体做法是:将包含文件的内容复制到包含语句(#include )处,得到新的文件,然后再对这个新的文件进行编译。




.c文件和.h文件的意义

.h为头文件.c为源文件,其实两者都是代码。

主要有几点好处:

  • .h头文件用于共享,只用一句#include就能包含,当然.c也可以包含;
  • 如果你要写库的话,可是你又不想暴露你的源代码,你可以把.c编译成.obj或是.lib发给别人用,然后把.h作为。

  所以一般情况下,.h里面全部都是声明,.c里面全部都是实现,有了.h就可以编译,有了.lib或你的.obj就可以连接。




文件包含形式

文件包含分为两种:

  • 包含.c文件
    包含.c文件编译多文件程序是不同的:
    多文件程序: 是在源文件编译时把多个文件进行编译、连接在一起生成一个可执行文件;
    包含.c文件: 按照我们上边的说法则是把多个文件合并为一个文件进行编译。


a.只包含 .c 文件

文件目录结构图

main.c 文件中

#include <stdio.h>
#include "LinearListStoragge.h"
#include "TestOne.c"  //导入TestOne.c 文件会报错
#pragma mark -- CustomClass

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

    orderListMethodCalling();
    
    int a = 5, b = 15;
    addMethod(a, b);
    
    return 0;
}

  这时Command+B,就会在Xcode中编译报错,如下图:

方法重复

  当时看别人编写C语言博客时,需要在main.c文件中要导入TestOne.c文件,需要加入#include "TestOne.c"。当时我也这么想也这么做的,但是我忽略了Xcode的强大,它把这件事当仁不让的做了,去掉 #include "TestOne.c"即可。在上面的代码段中,将 .h 文件去除(注释掉:#include "LinearListStoragge.h")也可以的,在 main 方法中也可以调用其方法。

main.c 文件

#include <stdio.h>
#pragma mark -- CustomClass

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

    orderListMethodCalling();
    
    int a = 5, b = 15;
    addMethod(a, b);
    
    return 0;
}

TestOne.c 文件


#include <stdio.h>


void addMethod(int a, int b){
    int c = a + b;
    printf(".c文件中的addMethod: %d\n", c);
}

输出:
.c文件中的addMethod: 20

  这个例子是采用包含.c文件 的方法实现的。

  在编译时,直接去编译main.c文件,预处理器会先把TestOne.c 文件中的内容复制到main.c中来,然后再对新的main.c进行编译。通过编译命令:

gcc main.c -o main

可以看到,这里并没有对TestOne.c进行编译,但还是生成了最终的main可执行程序。

也可以通过命令来观察一下预处理的结果:
编译命令:

   gcc -E main.c -o main.cpp

  通过在main.cpp文件末尾可以观察到,其实就是将TestOne.c文件中的内容添加到了main函数之前,然后对新的文件进行编译,生成最终的可执行程序。


编译多文件程序

  同样是上边的例子,现在编译器不是Xcode,我们需要在main.c中导入TestOne.c文件。但是我们把main.c中“#include "TestOne.c"”注释掉,加上一句:“extern int c;”因为 c 变量在文件TestOne.c中定义。

main.c 文件

#include <stdio.h>
//#include "TestOne.c"
//添加上这一句
extern int c;

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

    orderListMethodCalling();
    
    int a = 5, b = 15;
    addMethod(a, b);
    
    return 0;
}

TestOne.c 文件

#include <stdio.h>

int c = 0;
void addMethod(int a, int b){
     c = a + b;
    printf(".c文件中的addMethod: %d\n", c);
}

  这次如果还是按照上面的方法只编译main.c的话就会出错,因为变量c和函数addMethod(int a, int b)并没有在main.c中定义,所以编译时需要将TestOne.c一起编译:
编译命令:

    gcc -c main.c -o main.o                 #编译main.c
    gcc -c TestOne.c  -o TestOne.o          #编译TestOne.c 
    gcc main.o TestOne.o -o main            #用main.o TestOne.o生成main

两者的不足之处是:

  • 包含.c的文件: 容易产生"重定义",大家想想如果一个工程中有多个文件都同时包含了某一个件,那么这个被包含文件的内容就会被复制到多个文件中去,也就相当于每个包含该文件的文件中都定义被包含文件中的变量和函数,这样在链接时就会产生"重定义"错误。

  • 多文件分开编译: 这个比较好,不容易出现"重定义"之类的问题,这也是我们最常用的一种方法,但是并不是像上面这个例子中这样直接去用,而是使用"头文件"将各个.c文件联系起来。

  上边这个例子大家会发现,在main.c中需要加上“extern int c;”这样一句声明,如果包含的文件较多?如果全局变量较多?...这个我们可以省掉吗?回答是肯定的!方法就是给它写上一个头文件。


  • 包含.h文件
    main.c
#include <stdio.h>
#include "LinearListStoragge.h"


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

    orderListMethodCalling();
    
    int a = 5, b = 15;
    addMethod(a, b);
    
    return 0;
}




LinearListStoragge.h

extern int c;               //把c声明为外部可用的
void addMethod(int a, int b);   //addMethod函数的声明

LinearListStoragge.c

#include "LinearListStoragge.h"

int c = 0;
void addMethod(int a, int b){
    c = a + b;
    printf(".c文件中的addMethod: %d\n", c);
}



  由上面来看,在要用到fun.c中定义的函数或变量的文件中只要包含.h文件就可以了,是不是这样???Ha Ha,当然是了。。。

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

  前面说了头文件的方法也是模块化程序设计中的一种非常有利的手段。

  把同一类功能写到一个.c文件中,这样可以把它们划为一个模块,另外再对应的写上一个.h文件做它的声明。这样以后再使用这个模块时只需要把这两个文件添加进工程,同时在要使用模块内函数或变量的文件中包含.h文件就可以了。

  举个很实际的例子,在单片机、ARM或其他嵌入式开发中,每一个平台可能本身都有多种不同的硬件模块,使用时需要去写相应的驱动程序,这样就可以把各个硬件模块的驱动程序作为一个模块(比如lcd驱动对对应lcd.clcd.hIIC驱动对应I2C.cI2C.h等),当具体使用到某个模块时,只需要在将对应的.c和.h文件添加进工程,并在文件中包含对就的.h文件即可。

所以关于头文件的写法个人总结以下几点:

  • 对应的.c文件中写变量、函数的定义
  • 对应的.h文件中写变量、函数的声明
  • 如果有数据类型的定义 和 宏定义 ,请写的头文件(.h)中
  • 头文件中一定加上#ifndef...#define....#endif之类的防止重包含的语句
  • 模块的.c文件中别忘包含自己的.h文件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353