很多人对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文件

C_文件包含.h文件和包含.c文件总结的更多相关文章

  1. 配置apue的头文件apue.h和unp的头文件anp.h

    配置apue的头文件apue.h和unp的头文件anp.h 如果要使用gcc -g 来生成可调试文件一定要修改Make.defines.linux文件中的CFLAGS变量 修改为:CFLAGS=-an ...

  2. Visual Studio中头文件stdafx.h的作用

    在较新版的Visual Studio中,新生成的C++项目文件的的头文件夹下会默认有头文件stdafx.h,而源文件夹下则默认有源文件stdafx.cpp,手动将这些文件删除后,编译时系统还会报错.下 ...

  3. 在源文件(.c)和头文件(.h)中声明和定义的区别——C语言

    最近在看多文件编程的时候遇到的一个问题,本来以为理解了声明和定义的区别(然而并没有····),也算是重新认识了一次声明和定义,下面上代码 情形一:在源文件(.c)中 相信大部分读者对声明和定义的理解是 ...

  4. 第3章 文件I/O(2)_文件I/O系统调用及文件描述符

    2. 文件I/O系统调用及文件描述符 2.1 文件I/O系统调用 (1)主要函数 函数 功能 函数 功能 open() 打开文件 read() 读取文件 creat() 创建文件 write() 写入 ...

  5. APUE包含头文件"apue.h"问题

    下载源码 从unix高级编程书籍官网下载书籍的上的所有源码. wget http://www.apuebook.com/src.tar.gz 解压这个文件 tar -zxvf src.tar.gz 解 ...

  6. C++中#include包含头文件带 .h 和不带 .h 的区别

    C++中#include包含头文件带 .h 和不带 .h 的区别? 如 #include <iostream> 和 #include <iostream.h> 包含的东西有哪些 ...

  7. Path,Files巩固,题目:从键盘接收两个文件夹路径,把其中一个文件夹中(包含内容)拷贝到另一个文件夹中

    这个题目用传统的File,InputStream可以做,但是如果用Files,Path类做,虽然思路上会困难一些,但是代码简洁了很多,以下是代码: import java.io.IOException ...

  8. VFS 上传文件到sftp 报错 包含中文路径 或者中文文件名称

    之前用Apache commons-vfs工具进行ftp操作(FTP服务器是 FileZilla Server) 上传本地文件 到 ftp服务器上,如果文件名称 包含 中文 报错 org.apache ...

  9. java实现将包含多个<REC>的文件拆成若干只包含一个<REC>的文件

    遍历文件夹里的文件,将包含多个<REC>的文件拆成若干只包含一个<REC>的文件 package com.prepub; import java.io.BufferedRead ...

随机推荐

  1. Table '' is marked as crashed and should be repaired 解决方法

    解决方法: 找到mysql的安装目录的bin/myisamchk工具,在命令行中输入: myisamchk -c -r ../data/mysql/user.MYI 然后myisamchk 工具会帮助 ...

  2. STS 设置 注解提示

    1.window -> Preferences

  3. java 如何查看jdk版本&位数

      java 如何查看jdk版本&位数 CreateTime--2018年4月22日18:20:18 Author:Marydon 方式一:通过dos命令实现 win+R-->cmd-- ...

  4. fread与read的差别(文件io补充)

    这里有一个我们常常提出的问题就是fread,read的差别.(当然这两个分别代表了操作文件系统的两套不同的函数,包含open,read, write, seek 等). 一.他们的差别就是一个(rea ...

  5. 类的专有方法(__init__)

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #http://www.cnblogs.com/zyxstar2003/archive/2011/03/21/ ...

  6. java 和 C 代码运行效率的比较(整理)

    最近和朋友无意间讨论起了 有关java 和C 的 效率问题, (我是java 推介者, 他是 c 语言推介者, 他做的是嵌入式) 故,想通过网络查询一下, 总结一下,两者到底效率如何,其有何差异,原因 ...

  7. 【redis】常用命令

    三.常用命令    1)连接操作命令    quit:关闭连接(connection)    auth:简单密码认证    help cmd: 查看cmd帮助,例如:help quit         ...

  8. 【laravel5.4+vue.js】laravel 循环三维数组,解决:htmlentities() expects parameter 1 to be string, array given

    laravel循环三维数组   +++   vue.js循环三维数组  (数据均是以三维数组形式存在的) <form-item label="权限名称" prop=" ...

  9. Spring配置文件头信息

    代码如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:// ...

  10. 最新最全的iOS手机支付总结

    关于手机支付,我想简单总结一下,我想主要分成三大类: 第一类,就是我们最常见的应用内支付(IAP),例如APPStore里面我们可以付费下载一些APP或者游戏. 第二类,就是我们经常使用第三方支付,例 ...