C 语言基础-#define
#define是以"#"号开头的预处理指令,它是定义标识符,称呼有很多如:宏定义、宏替换、宏展开。
- 在预编译阶段起作用
- 单纯进行文本替换,没有类型,不做类型检查,也不能进行调试
- 只是代码的展开操作,不分配内存,占的是test段(代码段)空间
- 宏调用时,需要程序设计者自行确保宏调用参数的类型正确。
- 过多的使用宏定义,会增加代码长度,会使二进制文件变大,会增加编译时间
- 宏定义允许嵌套宏定义
主要功能:
- 可以用来定义常量
- 可以用来定义表达式,拆行用 \
- 可以用来定义函数代码块,拆行用 \
主要分为:
- 有参宏定义:有参宏的宏名后带参数。
- 无参宏定义:无参宏的宏名后不带参数。
使用时注意:
- 预处理指令:不是说明或语句,所以宏定义时,在行末不必加分号,如果加上分号则预编译时会连分号也一起置换
- 必须宏定义在函数之外,其作用域为宏定义命令起到源程序结束。
- 一般建议宏名用大写字母表示,以便于与变量区别,就看个人喜好啦。
无参宏定义
无参宏的宏名后不带参数。
- 定义格式:#define 标识符 XXX,XXX 可以是字面常量(基础类型、字符、字符串)、表达式(包括函数代码块)等。
定义字面常量:
#define A 10
#define B 'b'
#define C "123"
#define PI 3.14
// 允许嵌套,这里嵌套这已定义的PI
#define S PI*A
有参宏定义
有参宏的宏名后带参数。
- 定义格式:#define 标识符(参数列表) XXX,XXX 可以是字面常量(基础类型、字符、字符串)、表达式(包括函数表达式)等。
- 标识符后必须紧跟括号()
- XXX或它的参数通常要用括号()括起来以避免出错。
- 在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数,实参可以传表达式。
- 宏调用时,不仅会宏展开,而且会用实参去代换形参。
/**
有参宏函数
注意:如果调用者传参时,两者类型不一致,在编译时就会发出警告。
优点:节省空间(给形参节省)
缺点:浪费时间(主要浪费在编译时);没有语法检查,不安全。
*/
#define MAXVALUE1(x,y) ((x)>(y)?(x):(y))
/**
有参普通函数
优点:有语法检查
缺点:浪费空间。
*/
int maxValue(int x,int y){
return x > y ? x : y;
}
#define QUA(a,b) a*b
#define CAU(x) (x)*(x+x)
#define TEST_VALUE 0x00000006
#define TEST_VALUE_OTHER 0x00000007
// 无参宏函数
#define TEST_VALUE_MODE() \
{\
int tmp;\
int state;\
if(tmp > state){\
tmp = TEST_VALUE;\
}else{\
tmp = TEST_VALUE_OTHER;\
}\
}
宏函数 VS 函数
td{ /*全局设置文本居中*/ text-align:left; }属性 | #define | 函数 |
---|---|---|
发生时间 | 在源程序进行编译之前,即预处理阶段进行宏替换。 | 函数调用则发生在程序编译运行期间。 |
类型检查 | 不检查参数类型,既是宏的优点,即适用于多种数据类型,又是宏的一个缺点,即类型不安全。故在宏调用时,需要程序设计者自行确保宏调用参数的类型正确。 | 参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。 不进行语法检查 |
代码长度 | 宏定义的代码会被展开插入到程序代码段中,会增加代码的长度 | 函数代码只出现于一个地方;每次调用都是同一份代码,不影响代码长度 |
执行速度 | 仅是简单文本替换,不做任何语法或逻辑检查。速度更快 | 在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法;在运行阶段参数需入栈和出栈操作。速度相对较慢, |
是否分配内存 | 仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,不需要分配空间。 | 函数调用时,需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中,需要分配内存空间 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,故建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。 |
不合法参数 | 参数可能被替换到宏体中的多个位置,所以不合法的参数求值可能会产生不可预料的结果。 | 因会类型检查,更安全、更容易控制。 |
能否递归 | 不能递归调用 | 可以递归调用 |
宏定中的符号用法
td{ /*全局设置文本居中*/ text-align:left; }符号类型 | 说明 |
---|---|
# 字符串化操作符 | 给参数x加双引号,即转换成字符串,注意:其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前 |
## 连接操作符 | 将两个参数按字面值连接成一个参数。注意:两个参数的类型要一致。 |
#@ 字符化操作符 | 给参数x加上单引号,即转换成字符 |
\ 行继续操作符 | 宏定义中,一行写不下时,利用反斜杠“\”进行换行,反斜杠后不能有空格 |
参考文献
undef(终止宏作用域)
#undef 指令,用来终止 #define宏定义的作用域
- 定义格式:#undef 标识符,标识符表示要终止作用域的宏。
// 哪里声明就在哪里终止 宏A 的作用域
#undef A