# 标准库和标准头文件 源文件通过编译可以生成目标文件(例如 GCC 下的 .o 和 Visual Studio 下的 .obj),并提供一个头文件向外暴露接口,除了保护版权,还可以将散乱的文件打包,便于发布和使用。 实际上我们一般不直接向用户提供目标文件,而是将多个相关的目标文件打包成一个静态链接库(Static Link Library),例如 Linux 下的 .a 和 Windows 下的 .lib。 打包静态库的过程很容易理解,就是将多个目标文件捆绑在一起形成一个新的文件,然后再加上一些索引,方便链接器找到,这和压缩文件的过程非常类似。 C语言在发布的时候已经将标准库打包到了静态库,并提供了相应的头文件,例如 stdio.h、stdlib.h、string.h 等。 Linux 一般将静态库和头文件放在`/lib`和`/usr/lib`目录下,C语言标准库的名字是`libc.a`,大家可以通过`locate`命令来查找它的路径: ```bash $ locate libc.a /usr/lib/x86_64-redhat-linux6E/lib64/libc.a $ locate stdio.h /usr/include/stdio.h /usr/include/bits/stdio.h /usr/include/c++/4.8.2/tr1/stdio.h /usr/lib/x86_64-redhat-linux6E/include/stdio.h /usr/lib/x86_64-redhat-linux6E/include/bits/stdio.h ``` 在 Windows 下,标准库由 IDE 携带,如果你使用的是 Visual Studio,那么在安装目录下的`\VC\include`文件夹中会看到很多头文件,包括我们常用的 stdio.h、stdlib.h 等;在`\VC\lib`文件夹中有很多 .lib 文件,这就是链接器要用到的静态库。 大家也可以在当前工程的属性面板(在工程名处单击鼠标右键选择“属性”)中查看路径: ![img](http://c.biancheng.net/uploads/allimg/190122/1116033508-0.png) ANSI C 标准共定义了 15 个头文件,称为“C标准库”,所有的编译器都必须支持,如何正确并熟练的使用这些标准库,可以反映出一个程序员的水平: - 合格程序员: - 熟练程序员: - 优秀程序员: 除了C标准库,编译器一般也会附带自己的库,以增加功能,方便用户开发,争夺市场份额。这些库中的每一个函数都在对应的头文件中声明,可以通过 #include 预处理命令导入,编译时会被合并到当前文件。 ## 头文件路径 我们常说,引入编译器自带的头文件(包括标准头文件)用尖括号,引入程序自定义的头文件用双引号,例如: ``` #include //引入标准头文件 #include "myFile.h" //引入自定义的头文件 ``` 使用尖括号`< >`,编译器会到系统路径下查找头文件;而使用双引号`" "`,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大,我们完全可以使用双引号来包含标准头文件,例如: ``` #include "stdio.h" #include "stdlib.h" ``` ## 绝对路径和相对路径 理论上讲,我们可以将头文件放在磁盘上的任意位置,只要带路径包含进来就可以。以 Windows 为例,在 D 盘下创建一个自定义的文件夹,名字为`abc`,它里面有一个头文件叫做`xyz.h`,那么在程序开头使用`#include "D:\\abc\xyz.h"`就能够引入该头文件。 现在不妨假设 xyz.h 中有一个宏定义和一个变量: ``` #define NAME "C语言中文网"int age = 5; ``` > 我们不鼓励在头文件中定义变量,否则多次引入后会出现重复定义错误,这里仅是一个演示案例,并不规范。 下面的代码会输出头文件中的宏和变量: ``` #include #include "D:\\abc\xyz.h" int main(){ printf("%s已经 %d 岁了!\n", NAME, age); return 0; } ``` 运行结果: C语言中文网已经 5 岁了! #### 绝对路径 像`D:\\abc\xyz.h`这种从盘符开始、完整地描述文件位置的路径就是绝对路径(Absolute Path)。 绝对路径从文件系统的“根部”开始查找文件: 1) 在 Windows 下,根部就是 C、D、E 这样的盘符,例如`D:\\a.h`、`E:\images\123.jpg`、`E:/videos/me.mp4`、`D://abc/xyz.h`等,分隔符可以是正斜杠`/`也可以是反斜杠`\`,盘符后面的斜杠可以有一个也可以有两个。 2) Linux 没有盘符,根部就是`/`,例如`/home/xxx/abc.h`、`/user/include/module.h`等,分隔符只能是正斜杠`/`,比 Windows 简洁很多。 为了增强代码的可移植性,引入头文件时请尽量使用正斜杠`/`。 #### 相对路径 相对路径(relative path)是从当前目录(文件夹)开始查找文件;当前目录是指需要引入头文件的源文件所在的目录,这也是本文开头提到的“当前路径”。 以 Windows 为例,假设在`E:/cDemo/`中有源文件 main.c 和头文件 xyz.h,那么在 main.c 中使用`#include "./xyz.h"`语句就可以引入 xyz.h,其中`./`表示当前目录,也即`E:/cDemo/`。 如果将 xyz.h 移动到`E:/cDemo/include/`(main.c 所在目录的下级目录),那么包含语句就应该修改为`#include "./include/xyz.h"`;对于 main.c 来说,此时的“当前目录”依然是`E:/cDemo/`。 如果将 xyz.h 移动到`E:/`(main.c 所在目录的上级目录),那么包含语句就应该修改为`#include "./../xyz.h"`,其中`../`表示上级目录。`./../xyz.h`的意思是,在当前目录的上级目录中查找 xyz.h 文件。 如果将 xyz.h 移动到`E:/include`目录,那么包含语句就应该修改为`#include "./../include/xyz.h"`。 需要注意的是,我们可以将`./`省略,此时默认从当前目录开始查找,例如`#include "xyz.h"`、`#include "include/xyz.h"`、`#include "../xyz.h"`、`#include "../include/xyz.h"`。 上面介绍的相对路径的写法同样适用于 Linux,请大家亲自测试,这里不再赘述。 在实际开发中,我们都是将头文件放在当前工程目录下,非常建议大家使用相对路径,这样即使后来改变了工程所在目录,也无需修改包含语句,因为源文件的相对位置没有改变。 ## 系统路径 Windows 下的C语言标准库由 IDE 自己携带,Linux 下的C语言标准库一般在固定的路径下,总起来说,标准库不在工程目录下,要使用绝对路径才能引入头文件,这样每次切换平台或者 IDE 都要修改包含路径,非常不方便。 为了让头文件更加具有实践意义,Windows 下的 IDE 都可以为静态库和头文件设置默认目录。以 Visual Studio 为例,在当前工程名处单击鼠标右键,选择“属性”,在弹出的对话框中就可以看到已经设置好的路径,如下图所示: ![img](http://c.biancheng.net/uploads/allimg/190122/111H424C-0.png) 这些已经设置好的路径就是本文开头提到的“系统路径”。 当使用相对路径的方式引入头文件时,如果使用`< >`,那么“相对”的就是系统路径,也就是说,编译器会直接在这些系统路径下查找头文件;如果使用`" "`,那么首先“相对”的是当前路径,然后“相对”的才是系统路径,也就是说,编译器首先在当前路径下查找头文件,找不到的话才会继续在系统路径下查找。 而使用绝对路径的方式引入头文件时,< >和" "没有任何区别,因为头文件路径已经写死了(从根部开始查找),不需要“相对”任何路径。 总起来说,相对路径要有“相对”的目标,这个目标可以是当前路径,也可以是系统路径,`< >`和`" "`决定了到底相对哪个目标。