前言

本系列笔记根据B站网课BV1Dk4y1j7oj记录。

编译器的作用

C++程序首先通过编译器生成中间文件。

一般而言,编译器通过文件后缀名决定如何对其进行处理(如.h文件作为被视为头文件,.cpp文件被作为编译单元^1进行编译)。

编译器首先对程序进行预处理。常用的预处理命令有#include#define#if#ifdef#ifndefendif#pragma等。

这里只举例讲解其中的几个:

  1. #include
    该命令把所引用的头文件内容原封不动地直接粘贴到命令位置。

PS:#include指令的三种类型

  • #include<xxx.h>

这种#include指令所引用的头文件,编译时会去系统目录寻找。

  • #include”xxx.h”

这种#include指令所引用的头文件,编译时会去当前目录(即包含该#include指令的文件所在的目录)寻找,若没找到再去系统目录寻找。

  • #include <xxx>

同第一种,C++标准下的新写法。

  1. #define
    该命令查找文件中的特定字符并进行替换。(如#define int long long把文件中所有的int替换成long long)。

在预处理结束之后,编译器还会对程序进行语法检查、优化等步骤。

链接器的作用

编译器生成的中间文件经过链接器链接后生成最终的可执行文件。

要注意链接阶段可能发生的重定义问题。在C和C++中,声明(Declaration)和定义(Definition)非常不同^3,通常可以有多次声明,但只能有一次定义。

当编译 / 链接过程中出现对同一件东西(变量、函数、类……)的多次定义,就会导致重定义问题。

比如,一个头文件(a)中包含了某物的定义,而另一个头文件(b)包含了a,当一个cpp文件同时引用a和b时,预处理阶段会把a中内容复制进b,再把a和b都复制进最终文件中,链接阶段就会因重定义问题报错。

为了避免重定义问题,要注意.h文件中尽量只放声明,而.cpp文件中尽量只放定义。

也可以使用下面的头文件写法(被称为标准头文件格式)避免重定义问题。

1
2
3
4
5
6
#ifndef AAA_H//即“if not define”。只有没有定义过一个名为AAA_H(不固定,根据具体情境命名)的宏时,才编译该语句到endif语句间的内容。
#define AAA_H//定义一个名为AAA_H的宏。

//头文件具体内容。

#endif//即“end if”。结束条件编译。

该格式通过条件编译语句(#ifndef#endif),实现了只有在第一次引用该头文件时才复制其中内容到源文件中。

当重复引用该头文件时,文件中内容会因为已经存在宏AAA_H而不编译。