文章目录
- 编译预处理
- 预处理做的事情
- 包含头文件
- 宏定义
- 条件编译
- 解决头文件重复包含问题
- 方法一、#ifndef
- 方法二、#pragma once
- 编译和链接
- 源代码的组织
- 编译
- 链接
- 更多细节
编译预处理
C++程序编译的过程:预处理-》编译(优化、汇编)-》链接
预处理指令主要有三种:
- 包含头文件:
#include
- 宏定义:
#define (定义宏)、#undef (删除宏)
- 条件编译:
#ifdef #ifndef
预处理做的事情
- 处理#include 头文件包含指令
- 处理#ifdef #else #endif、#ifndef #else #endif 条件编译指令
- 处理#define 宏定义
- 为代码添加行号、文件名和函数名
- 删除注释
- 保留部分#pragma编译器指令(编译的时候会用到)
包含头文件
#include
包含头文件有两种方式:
#include<文件名>
:直接从编译器自带的函数库目录中寻找文件#include"文件名"
:先从自定义的目录中寻找文件,如果找不到,再从编译器自带的函数库目录
中寻找
include可以包含许多类型文件,本质就是将包含的文件复制到主文件中
宏定义
- 无参数的宏:
#define 宏名 宏内容
- 有参数的宏:
#define MAX(x,y) ((x)>(y)?(x):(y))
编译的时候,编译器把程序中的宏名用宏内容替换,称为宏展开
宏可以只有宏名,没有宏内容
C++常用宏:
- 当前源代码文件名:
__FILE__
- 当前源代码函数名:
__FUNCTION__
- 当前源代码行号:
__LINE__
- 编译的日期:
__DATE__
- 编译的时间:
__TIME__
- 编译的时间戳:
__TIMESTAMP__
- 区分gcc和g++:
__cplusplus
条件编译
最常用的两种:#iddef #ifndef
如果宏名存在,使用程序段一,不存在使用程序段二
#ifdef 宏名
程序段一
#else
程序段二
#endif
如果宏名不存在,使用程序段一,不存在使用程序段二
等价于:
如果宏名存在,使用程序段二,不存在使用程序段一
#ifdnef 宏名
程序段一
#else
程序段二
#endif
解决头文件重复包含问题
方法一、#ifndef
#ifndef __DD
#define __DD#endif
方法二、#pragma once
#pragma once
xxx
#ifndef
- 受C++语言标准支持,不受编译器的限制
- 可以针对文件中的部分代码
#pragma once
- 有的编译器不支持
- 只能针对整个文件
编译和链接
源代码的组织
头文件.h:#include
头文件、函数的声明、结构体的声明、类的声明、模板的声明、内联函数、#define 和 const 定义的常量等
源文件.cpp:函数的定义、类的定义、模板具体化的定义
主程序 main:主程序负责实现框架和核心流程,把需要用到的头文件用#include
包含进来
编译
将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化和汇编后,编译成若干个目标文
件(二进制文件)。
链接
将编译好的目标文件以及他们所需要的库文件链接在一起,形成一个整体
更多细节
- 分开编译好处:每次只编译修改过的源文件,然后再链接,效率更高
- 编译单个
*.cpp
文件的时候,必须要让编译器知道名称的存在,否则会出现找不到标识符的错误(直接或者间接包含头文件都可以) - 编译单个
*.cpp
文件的时候,编译器只需知道名称的存在,不会其定义一起编译 - 如果函数或类定义不存在,编译不会报错,但是链接会出现无法解析的外部命令
- 链接的时候,变量、函数和类的定义只能有一个,否则会出现重定义的错误。(如果把变量、函数和类的定义放在
*.h
文件中,*.h
会被多次包含,链接前可能存在多个副本;如果放在*.cpp
文件中,*.cpp
文件不会被包含,只会被编译一次,链接前只存在一个版本) - 把变量、函数和类的定义放在
*.h
中是不规范的做法,如果*.h
被多个*.cpp
包含,会出现重定义 - 用
#include
包含*.cpp
也是不规范的做法,原理同上 - 尽可能不使用全局变量,如果一定要用,要在
*.h
文件中声明(extern
关键字),在*.cpp
文件中定义 - 全局的const 常量在头文件中定义(const 常量仅在单个文件内有效)。
*.h
文件重复包含的处理方法只对单个的*.cpp
文件有效,不是整个项目。- 函数模板和类模板的声明和定义可以分开书写,但它们的定义并不是真实的定义,只能放在
*.h
文件中;函数模板和类模板的具体化版本的代码是真实的定义,所以放在*.cpp
文件中