#define MAX_COLS 20 /* 符号常量 */ #define SQUARE(x) ((x) * (x)) /* 宏:名字(参数) 替换体 —— 中间不能有空格 */ int a = MAX_COLS; /* 替换为 20 */ int b = SQUARE(3); /* 替换为 ((3) * (3)) = 9 */
替换是纯文本操作:宏参数在替换体里每次出现都被原文替换。不加括号的后果:
#define BAD(x) x * x int c = BAD(1 + 2); /* 变成 1 + 2 * 1 + 2 = 5,而不是 9! */ int d = SQUARE(1 + 2); /* ((1+2)*(1+2)) = 9 —— 参数加括号 */ int n = 5; int e = SQUARE(n++); /* ((n++)*(n++)):同一序列点两次修改,未定义! */
| 维度 | #define 宏 | 函数 |
|---|---|---|
| 开销 | 零调用开销,代码膨胀(每处展开) | 调用开销,代码一份 |
| 类型检查 | 无——文本替换,任何类型都能套 | 严格原型检查 |
| 副作用 | 参数重复求值(++ 危险) | 参数只求值一次 |
| 类型灵活性 | 天然泛型(对任何类型都「对」) | 类型固定(或用 void* 模拟) |
| 适用 | 短小、频繁、想要任意类型的计算 | 逻辑复杂、参数有副作用、需要递归 |
两个实用补充:#undef NAME 取消定义(重定义前必须先 undef);#define 名称 只判断「是否定义过」用于条件编译。命名约定:宏全大写,让调用方一眼识别「这是替换、小心副作用」。
#if defined(DEBUG) && DEBUG >= 2 /* 表达式形式 */
...调试代码...
#elif defined(TRACE)
...精简日志...
#endif
#ifndef DEBUG /* 是否被定义的简写 */
#define DEBUG 1
#endif
/* 头文件守卫:防止重复包含导致重定义 */
#ifndef POINTS_ON_C_LIST_H
#define POINTS_ON_C_LIST_H
/* ...头文件内容:原型、类型定义、宏... */
#endif| 写法 | 含义 | 用途 |
|---|---|---|
| #include <stdio.h> | 到系统目录找 | 标准库/第三方 |
| #include "list.h" | 先当前目录再系统目录 | 自己的头文件 |
头文件里只放声明(原型、类型、宏、extern 变量),不放定义(函数体、变量实体)——定义放 .c 文件,否则被两个单元包含就重定义。#include 嵌套靠守卫兜底。