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文件,我们最好不要污染。