1. 预定义符号由编译器内置预处理阶段直接生效可直接使用常用于日志 / 调试信息输出表格符号含义__FILE__当前编译的源文件名称__LINE__当前代码所在行号__DATE__文件编译的日期__TIME__文件编译的时间__STDC__编译器遵循ANSI C 标准时值为 1否则未定义示例printf(file:%s line:%d\n, __FILE__, __LINE__);而gcc是完全支持ANSI C标准2.#define定义常量基础语法#define name stuff本质纯文本替换预处理阶段直接把name替换为stuff换行过长内容可换行行尾加反斜杠\续航符关键避坑不要加分号;错误写法#define MAX 1000;风险替换后会多出分号破坏语法结构例如if-else语句中会出现语法错误。正确写法#define MAX 1000示例拓展#define reg register // 关键字别名 #define do_forever for(;;) // 无限循环简写 #define CASE break;case // case自动补全break3.#define定义宏带参数替换基础语法#define name(参数列表) stuff⚠️ 强制要求宏名与左括号之间不能有空格否则会被识别为常量而非宏。核心坑点运算符优先级问题坑 1参数未加括号#define SQUARE(x) x*x // 调用 SQUARE(a1) → 替换为 a1*a1运算顺序错误结果不符合预期✅ 解决每个参数都加括号#define SQUARE(x) (x)*(x)坑 2整体表达式未加括号#define DOUBLE(x) (x)(x) // 调用 10 * DOUBLE(a) → 10*(a)(a)乘法优先级高于加法结果错误✅ 解决整体表达式外层再加一层括号#define DOUBLE(x) ((x)(x)) 通用规则宏的参数、整体表达式都必须加括号规避运算符优先级问题。4. 带有副作用的宏参数副作用定义参数表达式求值后产生永久变化如x、x--。风险参数在宏内被多次使用时会被重复执行产生意外结果。示例#define SQUARE(x) (x)*(x) int a5; SQUARE(a); // 替换后(a)*(a)a会自增2次结果异常解决方案尽量用函数替代带副作用的宏若必须用宏避免传入自增 / 自减类参数。总结预定义符号用于快速获取编译信息适合调试#define常量禁止加分号宏定义必须给参数、整体表达式加括号宏避免使用带自增 / 自减的参数防止副作用。5. 宏替换规则3 步 2 个注意点3 步替换流程参数预处理调用宏时先把参数内部的#define符号替换完成文本插入替换将宏文本插入原位置参数名替换为传入的实参重复扫描整体再次扫描继续处理剩余#define符号递归替换2 条核心注意宏可以嵌套调用其他宏但不能递归调用自身字符串常量内的内容不会被预处理搜索替换。6. 宏 vs 函数 完整对比考点重点1. 宏的优势速度更快无函数调用、返回的栈开销直接文本替换执行类型无关不限制参数类型整形、浮点型通用不用重载多个函数可传类型参数能把int/char这种类型当参数传入函数做不到。// 示例宏实现动态内存分配 #define MALLOC(num, type) (type*)malloc(num * sizeof(type)) // 使用 MALLOC(10, int); // 替换后(int*)malloc(10 * sizeof(int));2. 宏的劣势代码膨胀每次调用都会复制代码频繁使用会让程序体积变大无法调试预处理阶段就完成替换不能打断点逐行调试运算符优先级坑不加括号易出错副作用风险x这类参数会被多次执行结果异常。3. 对比表格必背属性#define 宏函数代码长度每次调用复制代码易膨胀代码仅 1 份调用跳转执行执行速度快无调用开销慢有调用 / 返回开销运算符优先级易出错需手动加括号参数仅求值 1 次结果稳定副作用参数会多次执行结果异常参数只求值 1 次安全参数类型类型无关通用性强类型严格需重载调试不可调试可逐行调试递归禁止递归支持递归4. 总结计算简单的情况下可以使用宏计算相对复杂就使用函数ps有时候宏可以做到的事函数做不到 比如宏的参数可以出现类型 函数却做不到inline 内联函数可以把函数声明为内联函数 就在函数前加个inline内联函数具有了函数和宏的优点基本语法inline int add(int a, int b) { return a b; }调用时int c add(1,2);编译器预处理 / 编译后直接替换成int c 1 2;7. # 运算符字符串化运算符核心作用将宏参数直接转换为字符串字面量仅能用于带参数的宏操作俗称字符串化。示例解析#define PRINT(n) printf(the value of #n is %d, n);#includestdio.h //#运算符 字符串化 #define PRINT(val,format) printf(the value of #val is format\n,val) //val前面加上引号 就能把参数转换成字符串 int main() { int a 10; double f 3.15f; PRINT(a, %d); PRINT(f, %.2f); }调用PRINT(a);预处理展开printf(the value of a is %d, a);原理#n把传入的参数a转为字符串aC 语言中相邻双引号字符串会自动拼接最终输出the value of a is 10。8. ## 运算符连接运算符核心作用将宏的两个参数直接拼接成一个标识符常用来批量生成不同类型 / 名称的函数、变量。示例解析#define type##_max(type, x, y) return (xy?x:y);//##连接运算符 #includestdio.h//这里宏定义的是一个函数体 //经过##连接 max_type 是函数名 (type x, type y)是参数 type是返回类型 #define GENRIC_MAX(type) type max_##type(type x, type y)\ {\ return xy?x:y;\ } GENRIC_MAX(char) GENRIC_MAX(int) GENRIC_MAX(float) int main() { int a 10; int b 20; int m1 max_int(a, b); printf(%d\n, m1); return 0; }传入int生成int_max函数传入float生成float_max函数图中输出3int 类型、4.500000float 类型就是该宏批量生成函数的运行结果。补充说明##实际开发使用较少多用于底层框架、泛型封装、批量代码生成场景。9. 宏与函数的命名约定C 语言语法本身无法区分宏和函数行业通用规范宏名全部大写如PRINT、MAX用于区分普通标识符函数名不全部大写一般使用小驼峰 / 下划线命名如getMax、get_max10. #undef 取消宏定义 作用删除已定义的宏让宏失效如果要重新定义同名宏必须先用#undef清除旧定义。语法#undef 宏名示例#define MAX 100 #undef MAX // 取消MAX的定义 #define MAX 200 // 重新定义合法用途避免不同头文件的宏名冲突局部禁用某个宏重新定义宏11. 命令行定义宏-D 选项核心作用不用在代码里写 #define直接在编译命令行定义宏灵活切换程序版本。代码示例#include stdio.h int main() { int array[ARRAY_SIZE]; // ARRAY_SIZE 代码里没定义靠编译命令传值 int i 0; for(i 0; i ARRAY_SIZE; i) array[i] i; for(i 0; i ARRAY_SIZE; i) printf(%d ,array[i]); printf(\n); return 0; }编译指令Linux/gccgcc -D ARRAY_SIZE10 program.c-DDefine定义效果等价于在代码开头写#define ARRAY_SIZE 10应用场景同一套代码编译出不同版本内存小的机器-D ARRAY_SIZE5小数组内存大的机器-D ARRAY_SIZE100大数组12. 条件编译核心作用选择性编译代码可灵活控制部分代码是否参与编译常用于调试代码开关、跨平台兼容、版本区分无需删除注释代码修改宏定义即可切换。1. 示例代码解读#include stdio.h #define __DEBUG__ // 定义调试宏开启调试输出 int main() { int i 0; int arr[10] {0}; for(i 0; i 10; i) { arr[i] i; #ifdef __DEBUG__ printf(%d\n, arr[i]); // 调试时打印查看赋值是否成功 #endif //__DEBUG__ } return 0; }开启调试保留#define __DEBUG__编译时会执行printf关闭调试注释 / 删除#define __DEBUG__printf代码直接被预处理器忽略不参与编译。2. 类常用条件编译指令1基础单分支#if ... #endif不能用变量根据常量表达式真假控制编译表达式由预处理器提前求值#define __DEBUG__ 1 #if __DEBUG__ // 代码 #endif2多分支#if...#elif...#else...#endif类似if- else if -else支持多条件判断#if 常量表达式1 // 代码1 #elif 常量表达式2 // 代码2 #else // 代码3 #endif3判断宏是否定义高频用法指令含义等价写法#ifdef symbol若symbol已定义编译代码#if defined(symbol)#ifndef symbol若symbol未定义编译代码#if !defined(symbol)4嵌套条件编译用于跨平台适配Windows/Linux/Unix多层条件叠加#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif13、头文件的两种包含方式1. 本地文件包含#include filename查找策略优先在当前源文件所在目录查找找不到再去系统标准库路径查找均无则报编译错误。适用场景包含自定义头文件自己编写的.h文件。标准路径示例Linux/usr/includeVS2013C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include2. 库文件包含#include filename.h查找策略直接去系统标准库路径查找不检索当前目录。适用场景包含系统标准头文件如stdio.h、string.h。补充语法上可用包含库文件但查找效率更低语义不清晰不推荐。14. 嵌套文件包含与重复包含问题问题本质#include的本质是文本替换预处理器直接把头文件内容复制到源文件中。若多次包含同一个头文件会导致内容重复拷贝造成预编译后代码量冗余结构体、宏、函数声明重复定义触发编译报错。示例test.c多次#include test.htest.h内容会被复制多份。两种解决方案方案 1条件编译守卫通用、跨平台笔试高频考点在头文件开头结尾添加#ifndef __TEST_H__ #define __TEST_H__ // 头文件核心内容 void test(); struct Stu { int id; char name[20]; }; #endif //__TEST_H__原理第一次包含时定义宏__TEST_H__后续再次包含时#ifndef判定宏已定义跳过头文件内容。方案 2#pragma once编译器指令便捷但兼容性略差#pragma once // 头文件内容原理编译器保证该头文件只被包含 1 次语法更简洁。15. 高频笔试真题解1. 头文件中#ifndef/#define/#endif的作用防止头文件被重复包含避免结构体、宏、函数声明重复定义引发编译错误是跨平台通用的头文件保护机制。2.#include filename.h和#include filename.h的区别直接在系统标准库路径查找用于标准库头文件先在当前目录查找再查找系统路径用于自定义头文件。总结还有很多其他预处理指令 #error #pragma #line #pragma pack.........要自己去学习和牢固知识