首页 /  技术专区  /  C 宽屏模式 >

c头文件包含

1、文件包含(#include 文件)

1.1 #include是不是只能包含头文件?

很多同学往往有一个误解,认为include只能包含头文件,实际上不是的,include可以包含任何文件,比如包含普通的文本文件,其它.c文件,只不过我们很少包含普通的文本文件和.c文件而已。


1.1.1 为什么不包含普通文本文件

因为在普通文本文件里面,放的一般都不是代码,而是一些普通文本信息,包含到.c中后一般会导致编译不通过,所以包含普通文本文件的意义不大。

不过如果普通文本文件中的内容是能够被编译通过的话,include也是可以的。

例子

a.txt

void fun(int a)
{
    printf("a = %d\n", a);
}

helloworld.c

#include "a.txt"

int main(void)
{
    fun(100);
    return 0;
}

查看预编译后的结果:你会发现a.txt的内容被完整的包含在了hellowolrd.c中。

gcc helloworld.c完全没有是任何问题,./a.out也能够正常运行,打印结果为100。

如果你不想a.txt的内容被重复包含的话,可以加入#indef或者#pragma once。

不过我们不建议include 普通文本文件,因为这种属于非正规操作。


1.1.2 为什么不包含其它的.c文件

既然普通文本文件都能包含,自然包含.c文件也可以,但是包含.c文件存在一些个问题。

我们都知道在.c中会定义函数,如果.c中有extern属性的函数的话,不同的.c包含了同一个.c时,会导致函数被重复定义,链接时就会报错。

例子:

a.c 

static void fun(void)
{
...
}

b.c                                   

#include "a.c"  
                  
int main(void)                          
{
}

 c.c

#include "a.c"
...

编译:gcc b.c c.c 

报错:multiple definition of `fun',fun重复定义了

就算加上#ifndef #pragma once也没用,因为它们只能防止在同一文件中多次包含a.c,但是无法防止在不同的.c同时包含同一个a.c。

当然,我们只要使用static修饰fun,也就是将fun改为本地的(那么fun就只在本文件有效),此时倒是可以解决重复定义的问题,但是不管怎么说,我们不建议直接include "***.c",因为这也不是正规操作。

1.1.3 有同学经常问一个问题,可不可以将全局变量定义在.h中

当然可以,但是一般只有少数情况会这么做,大多数情况都是定义在.c中,因为放在.h中的话存在一些问题。

(1)如果将初始化的全局变量放在.h中的话,可能会带来大麻烦

xxx.h 

#ifnedf  H_XXX_H
#define  H_XXX_H
int flag = 100;
#endif

flag是extern修饰的,在全局都是可见的,如果这个时候a.c b.c c.c中都包含了xxx.h,这会导致每个.c中都包含了一个int flag = 100;,链接时就会报flag重复定义了,因为有重名的强符号。

此时怎么办?

可以加static修饰,比如

xxx.h 

#ifnedf  H_XXX_H
#define  H_XXX_H
static int flag = 100;
#endif

那么每个.c包含xxx.h时,每个.c中的flag就只在本文件中有效,防止了不同.c中flag的冲突,但是这样也存在问题,如果某个.c中碰巧也定义了一个flag,这也会导致冲突。

a.c

include "xxx.h"

int flag = 400;

展开后就变成了

a.c

static int flag = 100;

int flag = 400;

将a.c编译为a.o时,也会报错说flag冲突了,因为这两个都是强符号,显然会导致强符号的重名冲突,所以加static修饰也是不保险的。

(2)如果非要将变量放在.h中,只有出现以下描述的情况时,我们才会这么做

那就是希望这个变量时“全局”可见的,能够被其它的.c引用,此时才放到.h中,而且放到.h中时不能初始化,只能是未初始化的弱符号,因为弱符号是允许重名的。

xxx.h 

#ifnedf  H_XXX_H
#define  H_XXX_H
...
int flag;
...
#endif

疑问:我就是需要给以一个初始值,怎么办呢?

答:此时我们就需要将有初始化的强符号放在.c中,.h中还是只放未初始化的弱符号,此时这个弱符号其实就变成了强符号的声明。

a.c                                 

int flag = 100;    				

a.h

#ifnedf  H_A_H
#define  H_A_H
...
extern int flag; //这就是int flag = 100;的声明
...
#endif


b.c 

#include "a.h" //将声明extern int flag;包含到b.c中

int fun(void)
{
    flag = 1000;  //此时就可以b.c就使用flag了
}

所以只有出现以上情况时,我们才将未初始化的全局变量放到.h中,否则都是放在.c中。

总结:

        · 没有特殊情况,不要全局变量放到.h中

        · 只有当你希望这个变量是全局可见的时候,才放到.h,以方便其它的.c包含

        · 放到.h中时一定不能初始化,否则可能会到带来编译麻烦

        · 如果非要初始化的,这个初始化只能放在.c中,.h中的弱符号此时就是声明

1.1.4 <> 与 ""

(1)以<>包含头文件

1)如何搜寻.h

直接到指定的“系统路径”下查找你要包含的头文件,如果找到就包含,找不到就提示头文件不存在。

我们在第一章中就介绍过,通过gcc -v的详细信息就可以查看系统路径有哪些。

这些系统路径属于“原生”系统路径,也就是由编译器内定的系统路径。

2)我们自己能不能把某个路径变成系统路径呢?

可以,只需要给编译命令指定相应选项即可,比如以gcc为例,通过-I选项即可实现,比如:

gcc -I /home/zxf/Desktop/my_include ***.c ***.c

搜寻.c中通过<>所包含的.h时,除了会到“原生”的路径下搜寻外,也会到/home/zxf/Desktop/my_include这个系统路径下搜寻。

注意:指定的路径也可以是相对路径,而且-I /home/zxf/Desktop/my_include也可以放到最后面,比如:

gcc ***.c ***.c -I /home/zxf/Desktop/my_include


3)例子(Linux下的例子)

包含自己的头文件时,就可以将.h所在的路径加入“系统路径”,如此就可以使用<>来包含自己的.h文件。

· helloworld.h

#pragma once //防止重复包含
#define STR  "hello world"

假设helloworld.h所在的路径为/home/zxf/Desktop/my_include。

· helloworld.c

#include <stdio.h>     
#include <helloworld.h>

int main(void)
{
    printf("%s\n", STR);
    return 0;
}
gcc -I /home/zxf/Desktop/my_include helloworld.c

我们这里的例子是在Linux下测试的,如果你是在Windows下测试的,这个路径就应该是windows下的路径。

你自己去实现这个例子时,具体路径是多少,需要根据你自己的情况来定,而且也可以是相对路径。

gcc -I ./my_include/ helloworld.c -v

疑问:如果要好几个路径的话,怎么办?

答:很简单,比如:

gcc -I /home/zxf/Desktop/my_include  -I /home/zxf/Desktop/  helloworld.c

通过-I选项将路径加入了系统路径后,加-v显示详细信息时,你会发现“系统路径”多了一条/home/zxf/Desktop/my_include。

//在如下路径搜索<>所包含的头文件,
#include <...> search starts here:
 /******自己添加的系统路径******/
 /home/zxf/Desktop/my_include   
 
 /****** 原生系统路径 *****/
 /usr/lib/gcc/x86_64-linux-gnu/5/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

(2)以""包含头文件

1)如何搜寻.h

先到程序员自己指定的路径下寻找头文件,如果找不到再到“系统路径”下寻找头文件。

不管是在那个路径找到了.h,只要找到了就OK,其实我们通过""包含.h文件时,一般都是希望到自己所指定的路径下去搜寻。

程序员自己指定的路径可以是相对路径,也可以是绝对路径。

· 绝对路径

#include "/home/zxf/Desktop/my_include/helloworld.h"   //Linux
#include "c:\home\zxf\Desktopmy_include\helloworld.h"  //Windows

先到自己指定的绝对路径下搜寻,找不到才到系统路径下搜寻。

· 相对路径

#include "./my_include/helloworld.h"  
#include "../my_include/helloworld.h" 
#include "./helloworld.h"

先到相对路径下搜寻,找不到才到系统路径下搜寻。

如果相对路径是从./开始的,./可以省略,因为省略后默认就是./。

因此#include "./helloworld.h"等价于#include "helloworld.h" 

2)绝对路径和相对路径的起点

(a)绝对路径的起点

        · 在Linux下

        比如:#include "/home/zxf/Desktop/my_include/helloworld.h" 

        在Linux下,绝对路径的起点/。


        · 在Windows下

        比如:#include "c:\home\zxf\Desktopmy_include\helloworld.h"

        在windows下,绝对路径的起点为盘符(c:、d:)。


(b)相对路径的起点

        以包含.h的.c所在路径为起点去搜寻.h文件,比如:

        · #include "./my_include/helloworld.h" 

        ./:包含helloworld.h的helloworld.c所在的路径


        · #include "../my_include/helloworld.h"

        ../:包含helloworld.h的helloworld.c所在路径的上一级目录


        · #include "./helloworld.h"  

        前面说过,等价于#include "helloworld.h",其实就是直接到.c所在的路径下去找.h,找不到再到系统路径下找。

        所以当.h和.c在同一个目录下时,我们经常写都是#include "helloworld.h"这种方式。


(3)如何包含自己的头文件

        通过前面的介绍可知,包含我们自己的头文件时,使用<>和""方式都可以。

        1)以<>方式包含时需要将自己.h所在路径下入系统路径

            加入系统路径的方式有两种:

        (a)直接给编译器命令指定选项

                如果我们是直接使用“编译器命令”来编译链接程序的话,就是使用这种方式。

        (b)通过IDE图形界面设置

        如果是使用IDE来开发的话,就使用这种方式。


        2)以""方式包含时,可以在""中直接指定.h所在的路径

            在实际上的开发中,以上这两种方式都是经常用到,只不过""方式用的可能更多一些。

            特别是当.c和.h就在同一个目录下时,我们经常使用的就是#include "***.h"的方式。


        3)也可以将自己的.h复制编译器内定的系统路径下,此时也可以使用<>包含

            但是一般不这么做,因为编译器内定的系统路劲,放的是系统.h文件,我们最好不要污染。



头像
0/200
图片验证码