# 前言

我们在课程中写的都是代码量不大的小文件,但是在实际工程开发中,情况可就不一样了。但是那么多的代码,我们不可能将它们放在同一个文件中,肯定要分成不同的源文件。但是如何分解程序?如何实现程序之间的交流,就成了一个问题。本节中我们将探讨如何将一个大的工程分成不同的源程序,并且实现这些源程序之间的交流。我们首先介绍 C++ 的编译模型,也就是 C++ 源程序是如何编译成为机器可理解的二进制代码的。然后,我们讨论如何将一个大的工程分解为多个小的源程序,并且实现他们之间的交流。最后,我们探讨 C++ 的预处理器的工作模式。

# C++ 编译模型

C++ 是一种编译型的语言,即通过编译器将源代码转化为机器可以理解的二进制代码。其编译过程分为 3 个阶段:

  • 预处理阶段:扩展头文件,进行宏替换等
  • 编译阶段:将预处理后的高级语言代码转化为机器可以理解的二进制代码,即目标文件。在这个阶段,编译器会检查一些语法错误,如漏掉了 ;
  • 链接阶段:将生成的多个目标文件合并成一个最终的可执行文件

语法错误主要集中在编译阶段进行检查,而程序的一些其他问题则多半是出现在链接阶段。例如,程序可能定义了一个函数的原型,并且对他进行了调用,但是却没有实现这个函数。又或者,定义的函数原型和实现不一样,按照函数原型进行调用以后,程序找不到函数的实现。有的人会奇怪为什么这种错误会出现在链接阶段,是因为链接器没有在这个文件中找到函数原型时,他会本能的想到是不是在需要链接的其他文件中。如果在其他文件中也没有找到的话,链接器才会告诉你出现了链接错误。

# 模块化与分解

通常来说,对于一个大的工程问题,我们无法一口气思考到所有的细节。而在这种情况下,我们倾向于将问题分解成不同的模块,然后通过不同模块间的合作和交流来解决。但是,如何确定模块划分的粒度呢?毕竟越往下分,细节就越多。而在这个时候,我们会选择使用一些抽象的接口。举个例子,我们无需设计 C++ STL,只需要调用其提供给我们的接口,便可以实现多种功能。而这就是模块划分的尽头。
对于模块化,通常来说遵循 3 个原则:

  • 简单化:提供一个较为简单的接口
  • 可扩展:在需要的时候,我们可以在不改变接口的条件下改变其实现方式。
  • 可复用:接口足够泛用(使用泛型,模板之类),可以保证函数可以被用在多个不同的项目中

# C++ 预处理器

# 前言

在编写 C++ 程序时,我们通常把一个程序分为 file.hfile.cpp 两个部分。 file.h 中描述的是程序提供的类及函数接口(定义),而 .cpp 文件中描述的是其实现。此外,通常在 .h 文件的前后,会加上如下内容:

#ifndef StrUtils_Included
#define StrUtils_Included
#include <string>
using namespace std;
string ConvertToUpperCase(string input);
string ConvertToLowerCase(string input);
string IntegerToString(int value);
string DoubleToString(double value);
#endif

# include 头文件

其作用在于将头文件的内容复制到 #include 处。头文件分为两种,一种用 <> 包起,是 C++ 标准库中的文件;而另外一种用 "" 包起,是用户自定义的头文件,编译器会在当前工程文件夹下找。

# define 定义与替换

宏定义的基本格式是 define val replacement 。在进行宏替换时,做的不是值替换,而是普通的字符串的替换。即将程序中所有的 val 都替换成 replacement 。在进行宏替换时编译器并不理解这到底是什么东西,就是简单的左无脑替换。因此在替换时必须要注意,譬如:

#define a 5 + 10
...
int b = 2 * a;

在进行宏替换后,效果如下:

int b = 2 * 5 + 10;

而不是:

int b = 2 * (5 + 10);

因为它做的仅仅是简单的字符串替换。这种错误经常发生,而要避免这类错误的方法是:

  1. define 时使用 () 圆括号
  2. 使用 const 语句

# include guard

预处理语句可以通过条件判断来决定是否要定义某些文件,一个简单的格式如下:

ifndef...define...endif

这套语句的基本意思是:如果已经 #include 过上述文件,就不需要再定义一次了。C++ 工程文件之间相互 #include 是家常便饭,这样做是为了防止由于多次互相 #include 带来的重定义问题。上述语句只是一个较为简化的版本,更为完整的定义是:

#if statement
  ...
  #elif another-statement
  ...
  #elif yet-another-statement
  ...
  #else
  ...
  #endif

这其中的 statement 可以是条件判断语句,也可以是 define() 语句。做条件判断时,使用的必须是已经定义过的变量,如:

#if MY_CONSTANT > 137 // Legal
  #if MY_CONSTANT * 42 == MY_CONSTANT // Legal
  #if sqrt(MY_CONSTANT) < 4 // Illegal, cannot call function sqrt
  #if MY_CONSTANT == 3.14 // Illegal, can only use integral values

而使用 define 语句时,如果变量已经定义,则 define() 返回 true ,否则返回 false 。例如:

#if defined(MY_CONSTANT) // Evaluates to true.
  #if defined(OTHER_CONSTANT) // Evaluates to false.
  #if !defined(MY_CONSTANT) // Evaluates to false.

其判断结果被应用在 if 语句中。例如:

#if defined(A)
	cout << "A is defined." << endl;
#elif defined(B)
    cout << "B is defined." << endl;
#elif defined(C)
 	cout << "C is defined." << endl;
#else
	cout << "None of A, B, or C is defined." << endl;
#endif

这套语句的效果相当于将所有 include 的内容复制粘贴到源程序中,但是注释掉重复定义的部分。它和注释不一样之处在于,这套语句可以嵌套,而注释不能。

#

# 内联函数