目录介绍
- 19.1 头文件
- 19.1.1 头文件作用
- 19.1.2 基本结构
- 19.1.3 头文件包含
- 19.2 宏定义
- 19.2.1 什么是宏
- 19.2.2 基本语法
- 19.2.3 宏的使用
- 19.2.4 宏的作用域
- 19.2.5 宏与函数区别
- 19.2.6 宏的优缺点
- 19.3 头文件保护
- 19.3.1 理解头文件保护
- 19.3.2 头文件保护作用
- 19.3.3 示例说明
- 19.3.4 不用头文件保护
- 19.3.5 使用场景
- 19.3.6 头文件保护原理
- 19.3.7 头文件保护总结
- 19.4 预定义宏
- 19.4.1 标准预定义宏
- 19.4.2 编译器相关宏
- 19.4.3 操作系统相关宏
- 19.4.4 标准库相关宏
- 19.4.5 其他常用宏
- 19.4.6 预定义宏案例
- 19.5 条件编译
- 19.5.1 基本语法
- 19.5.2 常用指令
- 19.5.3 示例代码案例
- 19.6 错误/警告指令
- 19.6.1 错误指令
- 19.6.2 警告指令
- 19.6.3 使用的场景
- 19.6.4 注意事项
- 19.7 行控制
- 19.8 空指令
- 19.9 综合宏案例
- 19.9.1 日志记录宏
19.1 头文件
19.1.1 头文件作用
声明与定义分离:头文件通常包含类、函数、变量的声明,而源文件(.cpp)包含具体的实现。
代码复用:通过包含头文件,可以在多个源文件中复用相同的代码。
模块化:将代码划分为多个模块,便于管理和维护。
19.1.2 基本结构
一个典型的头文件包含以下内容:
- 防止重复包含的预处理指令(#ifndef, #define, #endif)。
- 类、函数、变量的声明。
- 必要的库头文件包含。
19.1.3 头文件包含规则
#include 用于将其他文件的内容插入到当前文件中。通常用于包含头文件。
#include <iostream> // 包含标准库头文件
#include "myheader.h" // 包含用户定义的头文件
< >:用于包含标准库头文件,编译器会在系统路径中查找。" ":用于包含用户定义的头文件,编译器会先在当前目录中查找,然后在系统路径中查找。
防止重复包含,使用预处理指令防止头文件被重复包含:
#ifndef HEADER_NAME_H
#define HEADER_NAME_H
// 头文件内容
#endif
(4)避免循环包含,如果两个头文件相互包含,会导致编译错误。可以通过前置声明(Forward Declaration)解决。
19.2 宏定义
19.2.1 什么是宏
在C++中,宏定义(Macro)是使用预处理器指令 #define 定义的符号常量或代码片段。宏在编译之前由预处理器处理,直接替换代码中的宏名称。
19.2.2 基本语法
#define MACRO_NAME value
MACRO_NAME:宏的名称,通常使用大写字母。value:宏的值,可以是常量、表达式或代码片段。
示例:定义常量
#define PI 3.14159
示例:定义代码片段
#define SQUARE(x) ((x) * (x))
19.2.3 宏的使用
示例:使用常量宏
double area = PI * radius * radius;
预处理器会将其替换为:
double area = 3.14159 * radius * radius;
示例:使用代码片段宏
int result = SQUARE(5);
预处理器会将其替换为:
int result = ((5) * (5));
19.2.4 宏的作用域
宏没有作用域,全局有效。可以使用 #undef 取消宏定义:
#define PI 3.14159
#undef PI
19.2.5 宏与函数区别
宏是文本替换,没有类型检查。
函数有类型检查,更安全。
19.2.6 宏的优缺点
优点
- 简单易用,直接替换代码。
- 可以用于条件编译和调试。
缺点
- 没有类型检查,容易出错。
- 难以调试,因为宏在预处理阶段被替换。
- 可能导致代码可读性差。
19.3 头文件保护
19.3.1 理解头文件保护
#ifndef, #define, 和 #endif 是 C++ 中的预处理指令,用于防止头文件被多次包含(即防止重复包含)。 这种技术称为 头文件保护 或 包含保护。
19.3.2 头文件保护作用
在 C++ 中,如果一个头文件被多次包含(例如,多个源文件都包含了同一个头文件),可能会导致重复定义错误。头文件保护通过条件编译来避免这种情况。
19.3.3 示例说明
示例
// File: MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
public:
void doSomething();
};
#endif // MYCLASS_H
#ifndef MYCLASS_H:检查是否未定义MYCLASS_H宏。如果未定义,则继续编译;如果已定义,则跳过整个头文件内容。#define MYCLASS_H:定义MYCLASS_H宏,表示该头文件已被包含。#endif:结束条件编译块。
优点
- 避免重复定义:如果多个源文件包含了同一个头文件,头文件保护可以防止类、函数或变量的重复定义。
- 提高编译效率:避免重复编译相同的内容,减少编译时间。
- 防止编译错误:避免因重复包含导致的编译错误,例如重复定义类或函数。
19.3.4 不用头文件保护
如果头文件没有使用头文件保护,可能会导致以下问题:
- 重复定义错误:如果多个源文件包含了同一个头文件,编译器会多次处理头文件的内容,导致重复定义错误。 例如:
// File: MyClass.h
class MyClass {
public:
void doSomething();
};
// File: main.cpp
#include "MyClass.h"
#include "MyClass.h" // 重复包含,导致编译错误
-
编译效率降低:头文件的内容会被多次编译,增加编译时间。
-
难以调试:重复定义错误可能难以定位,尤其是在大型项目中。
19.3.5 使用场景
所有头文件都应使用头文件保护,以确保代码的健壮性和可维护性。
19.3.6 头文件保护原理
头文件保护通过条件编译指令实现:
#ifndef MYCLASS_H:检查MYCLASS_H是否未定义。#define MYCLASS_H:定义MYCLASS_H,表示头文件已被包含。#endif:结束条件编译块。
当第一次包含头文件时,MYCLASS_H 未定义,头文件内容会被编译。
当第二次包含头文件时,MYCLASS_H 已定义,头文件内容会被跳过。
19.3.7 头文件保护总结
| 特性 | 使用头文件保护 | 不用头文件保护 |
|---|---|---|
| 重复定义错误 | 避免 | 可能导致 |
| 编译效率 | 提高 | 降低 |
| 代码健壮性 | 更健壮 | 容易出错 |
| 使用场景 | 所有头文件 | 不推荐 |
19.4 预定义宏
C++ 中的预定义宏是由编译器在编译时自动定义的宏,用于提供有关编译环境、编译器、操作系统、标准库等信息。这些宏可以帮助开发者编写跨平台的代码或根据不同的编译环境调整代码行为。
19.4.1 标准预定义宏
这些宏由 C++ 标准定义,所有符合标准的编译器都支持。
| 宏名 | 描述 |
|---|---|
__cplusplus | 表示 C++ 标准的版本。例如,C++11 为 201103L,C++14 为 201402L,C++17 为 201703L,C++20 为 202002L。 |
__DATE__ | 当前源文件的编译日期,格式为 "Mmm dd yyyy"(例如 "Oct 15 2023")。 |
__TIME__ | 当前源文件的编译时间,格式为 "hh:mm:ss"(例如 "14:30:45")。 |
__FILE__ | 当前源文件的文件名(包括路径)。 |
__LINE__ | 当前代码行的行号。 |
__func__ | 当前函数的名称(C++11 引入)。 |
__STDC__ | 如果编译器符合 C 标准,则定义为 1,否则未定义。 |
__STDC_HOSTED__ | 如果编译器是托管环境(支持完整的标准库),则定义为 1,否则为 0。 |
__STDC_VERSION__ | 表示 C 标准的版本(仅适用于 C 代码)。 |
19.4.2 编译器相关宏
这些宏由特定的编译器定义,用于标识编译器及其版本。
| 宏名 | 描述 |
|---|---|
__GNUC__ | GCC 编译器的主版本号。 |
__GNUC_MINOR__ | GCC 编译器的次版本号。 |
__GNUC_PATCHLEVEL__ | GCC 编译器的补丁版本号。 |
__clang__ | 如果使用 Clang 编译器,则定义为 1。 |
__clang_major__ | Clang 编译器的主版本号。 |
__clang_minor__ | Clang 编译器的次版本号。 |
__clang_patchlevel__ | Clang 编译器的补丁版本号。 |
_MSC_VER | Microsoft Visual C++ 编译器的版本号。例如,MSVC 2019 为 1920。 |
_MSC_FULL_VER | Microsoft Visual C++ 编译器的完整版本号。 |
__INTEL_COMPILER | Intel 编译器的版本号。 |
19.4.3 操作系统相关宏
这些宏用于标识目标操作系统。
| 宏名 | 描述 |
|---|---|
_WIN32 | 如果目标系统是 Windows(32 位或 64 位),则定义为 1。 |
_WIN64 | 如果目标系统是 64 位 Windows,则定义为 1。 |
__linux__ | 如果目标系统是 Linux,则定义为 1。 |
__APPLE__ | 如果目标系统是 macOS 或 iOS,则定义为 1。 |
__unix__ | 如果目标系统是 Unix 或类 Unix 系统,则定义为 1。 |
__ANDROID__ | 如果目标系统是 Android,则定义为 1。 |
__CYGWIN__ | 如果目标系统是 Cygwin(Windows 上的 Unix 环境),则定义为 1。 |
19.4.4 标准库相关宏
这些宏用于标识使用的标准库及其版本。
| 宏名 | 描述 |
|---|---|
__GLIBC__ | GNU C 库(glibc)的主版本号。 |
__GLIBC_MINOR__ | GNU C 库(glibc)的次版本号。 |
_LIBCPP_VERSION | LLVM 的 libc++ 标准库版本号。 |
_MSVC_STL_VERSION | Microsoft Visual C++ 标准库版本号。 |
19.4.5 其他常用宏
| 宏名 | 描述 |
|---|---|
NDEBUG | 如果定义了 NDEBUG,则禁用 assert 宏。通常用于发布模式。 |
__has_include | 检查是否包含某个头文件(C++17 引入)。 |
__has_cpp_attribute | 检查是否支持某个 C++ 属性(C++20 引入)。 |
19.4.6 预定义宏案例
以下是一个使用预定义宏的示例:
#include <iostream>
int main() {
std::cout << "C++ version: " << __cplusplus << std::endl;
std::cout << "Compilation date: " << __DATE__ << std::endl;
std::cout << "Compilation time: " << __TIME__ << std::endl;
std::cout << "Current file: " << __FILE__ << std::endl;
std::cout << "Current line: " << __LINE__ << std::endl;
std::cout << "Current function: " << __func__ << std::endl;
#ifdef __GNUC__
std::cout << "GCC version: " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__ << std::endl;
#endif
#ifdef _MSC_VER
std::cout << "MSVC version: " << _MSC_VER << std::endl;
#endif
#ifdef __linux__
std::cout << "Running on Linux" << std::endl;
#elif _WIN32
std::cout << "Running on Windows" << std::endl;
#elif __APPLE__
std::cout << "Running on macOS" << std::endl;
#endif
return 0;
}
19.5 条件编译
C++ 中的 条件编译 是一种在编译时根据特定条件选择性地包含或排除代码的技术。它通过预处理器指令(如 #if、#ifdef、#ifndef、#else、#elif 和 #endif)实现。
条件编译通常用于编写跨平台代码、启用或禁用调试信息、根据编译环境调整代码行为等。
19.5.1 基本语法
以下是条件编译的基本语法:
#if condition
// 如果 condition 为真,编译此部分代码
#elif another_condition
// 如果 another_condition 为真,编译此部分代码
#else
// 如果前面的条件都不为真,编译此部分代码
#endif
19.5.2 常用指令
| 指令 | 描述 |
|---|---|
#if | 如果条件为真,编译后续代码。 |
#ifdef | 如果宏已定义,编译后续代码。 |
#ifndef | 如果宏未定义,编译后续代码。 |
#else | 如果前面的条件为假,编译后续代码。 |
#elif | 如果前面的条件为假且当前条件为真,编译后续代码。 |
#endif | 结束条件编译块。 |
#define | 定义宏。 |
#undef | 取消定义宏。 |
19.5.3 示例代码案例
示例 1:根据平台选择代码
#include <iostream>
int main() {
#ifdef _WIN32
std::cout << "Running on Windows" << std::endl;
#elif __linux__
std::cout << "Running on Linux" << std::endl;
#elif __APPLE__
std::cout << "Running on macOS" << std::endl;
#else
std::cout << "Unknown platform" << std::endl;
#endif
return 0;
}
示例 2:启用或禁用调试信息
#include <iostream>
#define DEBUG
int main() {
#ifdef DEBUG
std::cout << "Debug mode is enabled" << std::endl;
#else
std::cout << "Debug mode is disabled" << std::endl;
#endif
return 0;
}
19.6 错误/警告指令
C++ 中的 错误/警告指令 是预处理器指令,用于在编译时生成自定义的错误或警告信息。这些指令可以帮助开发者在代码中标记潜在问题、强制约束条件或提供调试信息。
19.6.1 错误指令
#error 指令用于在编译时生成一个错误消息,并终止编译过程。通常用于强制检查某些条件或标记不支持的配置。
语法
#error "错误消息"
示例
#if __cplusplus < 201103L
#error "This code requires C++11 or later."
#endif
如果编译器不支持 C++11,编译时会输出错误消息并终止:
error: This code requires C++11 or later.
19.6.2 警告指令
#warning 指令用于在编译时生成一个警告消息,但不会终止编译过程。通常用于提醒开发者注意某些问题或潜在风险。
语法
#warning "警告消息"
示例
#ifdef DEBUG
#warning "Debug mode is enabled. Performance may be affected."
#endif
如果 DEBUG 宏已定义,编译时会输出警告消息:
warning: Debug mode is enabled. Performance may be affected.
19.6.3 使用的场景
场景 1:检查编译器版本
#if __cplusplus < 201703L
#error "This code requires C++17 or later."
#endif
场景 2:检查平台支持
#ifndef _WIN32
#error "This code is only supported on Windows."
#endif
场景 3:提醒未完成的功能
#warning "This feature is under development and may not work as expected."
场景 4:检查宏定义
#ifndef MY_CUSTOM_MACRO
#error "MY_CUSTOM_MACRO must be defined."
#endif
场景 5:提醒弃用的 API
#warning "This API is deprecated. Use the new API instead."
19.6.4 注意事项
#error和#warning的区别:#error会终止编译过程,而#warning` 只会生成警告消息,编译会继续。- 跨编译器支持:
#error是所有 C++ 编译器都支持的,但#warning并非所有编译器都支持(例如 MSVC 不支持#warning)。 - 消息内容:错误或警告消息应尽量清晰明确,帮助开发者快速定位问题。
19.9 综合宏案例
19.9.1 日志记录宏
下面是一个使用 #define 和 do { ... } while (0) 的宏案例,并详细说明其作用和原理。
案例:日志记录宏
#define LOG_IF_ERROR(ret, msg) \
do { \
if (ret != SUCCESS) { \
std::cerr << "ERROR: " << msg << " (Code: " << ret << ")" << std::endl; \
} \
} while (0)
案例作用
- 功能:该宏用于检查返回值
ret是否表示错误(ret != SUCCESS),如果是,则输出错误信息到标准错误流(std::cerr)。 - 用途:在代码中快速检查函数返回值,并在发生错误时记录日志,方便调试和问题排查。
使用示例
#include <iostream>
#define SUCCESS 0
int SomeFunction() {
// 模拟一个可能失败的操作
return -1; // 返回错误码
}
int main() {
int ret = SomeFunction();
LOG_IF_ERROR(ret, "SomeFunction failed");
return 0;
}
输出:
ERROR: SomeFunction failed (Code: -1)
原理分析
#define是 C/C++ 中的预处理指令,用于定义宏。- 宏在编译前会被预处理器展开为代码。例如,
LOG_IF_ERROR(ret, "SomeFunction failed")会被展开为:do { if (ret != SUCCESS) { std::cerr << "ERROR: " << "SomeFunction failed" << " (Code: " << ret << ")" << std::endl; } } while (0); do { ... } while (0)是一种常见的宏定义技巧,用于将多行代码封装成一个逻辑块。- 作用:
- 确保宏在展开后能够正确工作,尤其是在条件语句或循环中使用时。
- 避免宏展开后与上下文代码产生语法错误。
- 示例:
如果没有
do { ... } while (0),宏展开后可能会导致问题。例如:展开后:if (condition) LOG_IF_ERROR(ret, "SomeFunction failed"); else DoSomethingElse();这样语法是正确的。如果没有if (condition) do { if (ret != SUCCESS) { std::cerr << "ERROR: " << "SomeFunction failed" << " (Code: " << ret << ")" << std::endl; } } while (0); else DoSomethingElse();do { ... } while (0),展开后会导致else与if不匹配。
4. 错误日志
- 如果
ret不等于SUCCESS,则输出错误信息到标准错误流(std::cerr)。 - 错误信息包括:
- 自定义的错误描述(
msg)。 - 错误码(
ret)。
- 自定义的错误描述(
优化建议,在错误信息中包含函数名称或行号,方便定位问题:
#define LOG_IF_ERROR(ret, msg) \
do { \
if (ret != SUCCESS) { \
std::cerr << "ERROR: " << msg << " (Code: " << ret << ") at " << __FILE__ << ":" << __LINE__ << std::endl; \
} \
} while (0)
输出:
ERROR: SomeFunction failed (Code: -1) at example.cpp:10
通过 #define 和 do { ... } while (0),可以定义功能强大且安全的宏。这种技巧在日志记录、错误处理等场景中非常有用,能够提高代码的简洁性和可维护性。