阅读说明:前面有些名词不懂没关系,后面都会解释
头文件
有两种文件拓展名.h 和.hpp(将.cpp的实现代码混入.h中变成.hpp文件—一般用于类模板)
头文件布局
- 预处理块 (带#的)
- 防卫式声明
- 预处理指令
- 前置声明
- 类声明
- 函数声明
头文件的处理
先大致解释一下编译器和预编译器的作用
编译器
将程序员写的代码转换为机器可读的二进制指令
预编译器
处理#开头的指令,进行文本替换(在指令处展开被包含的文件)。可以放在程序中的任何位置。
头文件本身不直接参与编译,头文件先被预编译器处理,再由编译器间接编译
处理预编译指令,处理规则如下
- 将所有的"#define"删除,并且展开所有的宏定义
- 处理所有的条件预编译指令,比如"#if"、“#ifdef”、“endif”
- 处理"#include"预编译指令,将被包含的文件直接插入到预编译指令的位置。
- 删除所有的注释
- 添加行号和文件标识,以便于编译时产生调试用的行号及编译错误警告行号。
- 保留所有的#pragma,因为编译器要使用他们,预编译器无法处理
预处理指令(预编译指令)
预处理指令结构
是以#开头的代码行,#后是指令关键字
预处理主要功能
-
1. 宏定义 #define 语法
#define 标识符 值 // (👆大写)(👆可以是一个常量表达式或字符串常量,或带参数--带参数的宏类函数调用)
-
2. 条件编译: 根据表达式的值或某个特定的宏是否被定义来确定哪些代码被编译,而哪些是不被编译的
- #if和#endif终止:根据表达式的值确定编译条件
- #ifdef和#ifndef终止:根据某个特定的宏是否被定义确定编译条件
- #else:用于某个#if/#ifdef指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码
- #elif:综合了#else和#if指令的作用(类else if())
-
3. 文件包含 #include
-
预编译器要想处理头文件必须要能找到这个头文件,根据其查找方式分为两种使用头文件的方式
#include <code.h> //一般用于包含系统文件 //在系统头文件所在的路径下(系统目录,也叫头文件的标准目录)开始找 #include "code.h" //一般用于包含项目文件 //以源文件所在位置(当前目录,也叫项目目录)开始查找,如果找不到再去系统目录找
当在这些路径中找不到include的头文件时就会抛出错误“fatal error: ***.h: No such file or directory”
-
防卫式声明
作用
如果这个头文件没有被定义,那就定义它,如果已经定义过了,那就忽略。
防止由于同一个头文件被包含多次,而导致了重复定义(Redefinition)。
两种
- 宏定义(header guard,亦称include guard或macro guard)
#ifndef _FILENAME_
#define _FILENAME_
//...
#endif
//#ifndef 依赖于宏定义名,当宏已经定义时,#endif之前的代码就会被忽略,但是这里需要注意宏命名重名的问题;
- 编译器指令
#pragma once
两种方式的比较
-
#pragma once
相比 宏定义 具有两个优点:① 更快。编译器不会第二次读取标记#pragma once的文件,但却会读若干遍使用header guard 的文件(寻找#endif);
② 更简单。不再需要为每个文件的header guard取名,避免宏名重名引发的"找不到声明"问题。
-
缺点则是:
#pragma once只能保证物理上的同一个文件不会被编译多次,但是当两个不同的文件内容相同时,pragma不能保证它们不被重复包含(不过这种重复包含很容易被发现并修正)。而且这是微软提供的编译器命令,当代码需要跨平台时,需要使用宏定义方式。
部分预处理指令
前置声明
作用
是变量/常量、类、函数、模板的纯粹声明
注意事项
- 类的成员函数无法单独做前置声明
- 只能定义指向前置声明的类的指针或引用
什么时候该用前置声明,什么时候该用#include?
- 尽量避免前置声明那些定义在其它项目中的实体,优先使用#include。
- 对于函数,应总是使用#include。
- 类模板应该优先使用#include。
常用系统头文件
#include<iostream> //I/O流(cin>>,cout<<...)
#include<algorithm>//常用算法
#inclde<cmath>//数学函数(如max(),min(),abs()...),继承自C语言的math.h
#include<string>//字符串
#include<cstdio>//C语言
//数据结构类:
#include<queue>//普通队列
#include<deque>//双向队列
#include<stack>//栈
#include<list>//列表
#include<vector>//动态数组
#include<map>//图
#include<set>//集合