(十一) 命名空间、头文件与多文件程序
函数逐渐增多后,可以把加法工具与主程序放到不同文件;下面围绕一次跨文件调用,解释声明、头文件、命名空间和链接。
1. 命名空间与同名标识符
题目原文(自拟演示): 为两套整数工具分别定义名为 calculate 的函数,一套求和、一套求差;对 2、3 调用两函数,输出 5、-1。
#include <iostream>
namespace addition {
int calculate(int a, int b) {
return a + b;
} // 名字属于 addition。
} // namespace addition
namespace subtraction {
int calculate(int a, int b) {
return a - b;
} // 同名但属于另一个空间。
} // namespace subtraction
int main() {
std::cout << addition::calculate(2, 3) << '\n'; // :: 明确选求和工具。
std::cout << subtraction::calculate(2, 3) << '\n';
}using addition::calculate; 可在当前作用域引入这个指定名字,using namespace addition; 则让该空间的名字参与查找。若把多个拥有同名函数的空间都引入,再只写 calculate,可能无法判断应该选择哪一个。学习程序用 using namespace std 减少重复前缀,项目头文件通常避免全局这样写。
2. 多文件程序的声明、定义与链接
头文件通常放声明,源文件放定义。自定义头文件可使用包含保护,避免同一份声明或类型定义被重复包含。#ifndef 检查标记是否尚未定义,#define 设置标记,#endif 结束这一段预处理判断;这些不是运行中的 if 分支。标准头文件常用尖括号,项目头文件常用双引号并按项目路径寻找。
math_utils.h:
// 头文件保护避免重复定义;这里只声明函数,不提供函数体。
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
namespace math_utils {
int add(int a, int b);
}
#endifmath_utils.cpp:
// 在源文件中提供命名空间内函数的定义,与头文件声明一致。
#include "math_utils.h"
int math_utils::add(int a, int b) {
return a + b;
}main.cpp:
// 调用另一个源文件实现的函数,链接时需要同时加入 math_utils.cpp。
#include <iostream>
#include "math_utils.h"
int main() {
std::cout << math_utils::add(2, 3) << '\n';
}main.cpp 看见声明就能编译调用,math_utils.cpp 提供函数体;链接阶段把这个调用与实现连接。修改头文件接口后,也要重新编译使用它的源文件。
三个文件放在同一目录后,执行:
g++ -std=c++17 main.cpp math_utils.cpp -o main
普通函数与全局变量通常在一个源文件中定义,头文件提供声明,让其他文件知道怎样使用。inline 函数可以按相应规则把定义放在头文件中,供多个源文件包含;至于机器代码是否把调用展开,则由编译器决定。模板定义为什么也常放在头文件中,下一章会结合实例化解释。
头文件不是运行时被“打开并执行”的脚本,也不是看到声明就已有实现。include 保护解决同一编译单元内重复包含,不能替代跨源文件的定义规则;项目扩大时,应检查声明、定义和参与链接的文件三者是否一致。