什么是预处理器?

C预处理器是编译器在真正编译代码之前执行的一个特殊程序。你可以把它想象成一个”文本处理工具”,它在编译前对你的代码进行各种修改和准备工作。

预处理器的工作流程

源代码 → 预处理器 → 修改后的源代码 → 编译器 → 最终程序

所有预处理指令都以#开头(如#include, #define),它们不是真正的C语句,而是给预处理器的命令。

宏定义 (#define)

宏就像是代码文本的简写方式——用一个简短的名字代表一大段代码或一个常量值。

1. 对象式宏(常量定义)

#define PI 3.1415926 // 定义圆周率
#define MAX_SIZE 100 // 定义最大尺寸

使用后,预处理器会在编译前把所有PI替换为3.1415926,MAX_SIZE替换为100。

2. 函数式宏(带参数的宏)

#define SQUARE(x) ((x) * (x)) // 计算平方
#define MAX(a, b) ((a) > (b) ? (a) : (b)) // 取最大值

注意:括号很重要!因为宏是直接文本替换,不加括号可能导致运算顺序错误。

宏使用注意事项

  • 宏名通常使用大写字母,以便与变量区分
  • 宏不是函数,只是文本替换
  • 避免在宏中使用副作用表达式(如x++)
  • 复杂的宏可以用do { … } while(0)包裹

文件包含 (#include)

用于将其他文件的内容插入到当前文件中,就像复制粘贴一样。

两种包含方式

#include <stdio.h> // 系统头文件,使用尖括号
#include “myheader.h” // 用户自定义头文件,使用双引号

查找路径区别:

尖括号:只在编译器系统目录中查找

双引号:先在当前目录查找,再到系统目录查找

防止重复包含

使用头文件保护机制,防止同一个头文件被多次包含:

#ifndef MY_HEADER_H
#define MY_HEADER_H

// 头文件内容

#endif

或者使用更简洁的方式:

#pragma once // 大多数现代编译器支持

条件编译

告诉预处理器根据条件选择性地包含或排除代码部分,就像代码的”开关”。

基本形式

#if 条件
    // 条件为真时包含的代码
#elif 其他条件
    // 其他条件为真时的代码
#else
    // 以上条件都不满足时的代码
#endif

常见用途

  • 调试代码: #ifdef DEBUG … #endif
  • 平台特定代码: #if defined(WIN32) … #endif
  • 功能开关: #if FEATURE_ENABLED … #endif
  • 防止头文件重复包含: 如前所述
Windows专用代码
#if _WIN32
// Windows专用代码
#endif
调试模式
#define DEBUG 1
#if DEBUG
printf(“Debug info”);
#endif

其他重要指令

#undef

取消已定义的宏:

#define TEST 1
// … 使用TEST …
#undef TEST // 后面TEST不再可用

#error

产生错误消息并停止编译:

#if !defined(VERSION)
    #error “VERSION is not defined!”
#endif

#pragma

编译器特定指令(因编译器而异):

#pragma warning(disable: 4996) // 禁用VS特定警告
#pragma pack(1) // 设置结构体对齐方式

预定义宏

编译器提供的无需定义的宏:

__LINE__ // 当前行号
__FILE__ // 当前文件名
__DATE__ // 编译日期
__TIME__ // 编译时间
__cplusplus // 用于C++编译