C 语言基础-宏定义(浅谈有参和无参宏定义,宏函数与普通函数的区别)

C 语言基础-#define

#define是以"#"号开头的预处理指令,它是定义标识符,称呼有很多如:宏定义、宏替换、宏展开。

  • 在预编译阶段起作用
  • 单纯进行文本替换,没有类型,不做类型检查,也不能进行调试
  • 只是代码的展开操作,不分配内存,占的是test段(代码段)空间
  • 宏调用时,需要程序设计者自行确保宏调用参数的类型正确。
  • 过多的使用宏定义,会增加代码长度,会使二进制文件变大,会增加编译时间
  • 宏定义允许嵌套宏定义

主要功能:

  • 可以用来定义常量
  • 可以用来定义表达式,拆行用 \
  • 可以用来定义函数代码块,拆行用 \

主要分为:

  • 有参宏定义:有参宏的宏名后带参数。
  • 无参宏定义:无参宏的宏名后不带参数。

使用时注意:

  • 预处理指令:不是说明或语句,所以宏定义时,在行末不必加分号,如果加上分号则预编译时会连分号也一起置换
  • 必须宏定义在函数之外,其作用域为宏定义命令起到源程序结束。
  • 一般建议宏名用大写字母表示,以便于与变量区别,就看个人喜好啦。

无参宏定义

无参宏的宏名后不带参数。

  • 定义格式:#define 标识符 XXXXXX 可以是字面常量(基础类型、字符、字符串)、表达式(包括函数代码块)等。

定义字面常量:

#define A 10
#define B 'b'
#define C "123"

#define PI 3.14
// 允许嵌套,这里嵌套这已定义的PI
#define S PI*A

有参宏定义

有参宏的宏名后带参数。

  • 定义格式:#define 标识符(参数列表) XXXXXX 可以是字面常量(基础类型、字符、字符串)、表达式(包括函数表达式)等。
  • 标识符后必须紧跟括号()
  • 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 函数

  
属性 #define 函数
发生时间 在源程序进行编译之前,即预处理阶段进行宏替换。 函数调用则发生在程序编译运行期间。
类型检查 不检查参数类型,既是宏的优点,即适用于多种数据类型,又是宏的一个缺点,即类型不安全。故在宏调用时,需要程序设计者自行确保宏调用参数的类型正确。 参数类型检查严格。程序在编译阶段,需要检查实参与形参个数是否相等及类型是否匹配或兼容,若参数个数不相同或类型不兼容,则会编译不通过。 不进行语法检查
代码长度 宏定义的代码会被展开插入到程序代码段中,会增加代码的长度 函数代码只出现于一个地方;每次调用都是同一份代码,不影响代码长度
执行速度 仅是简单文本替换,不做任何语法或逻辑检查。速度更快 在编译阶段需要检查参数个数是否相同、类型等是否匹配等多个语法;在运行阶段参数需入栈和出栈操作。速度相对较慢,
是否分配内存 仅是简单的文本替换,且替换完就把宏名对应标识符删除掉,不需要分配空间。 函数调用时,需要为形参分配空间,并把实参的值复制一份赋给形参分配的空间中,需要分配内存空间
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,故建议宏在书写的时候多些括号。 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
不合法参数 参数可能被替换到宏体中的多个位置,所以不合法的参数求值可能会产生不可预料的结果。 因会类型检查,更安全、更容易控制。
能否递归 不能递归调用 可以递归调用

宏定中的符号用法

  
符号类型 说明
# 字符串化操作符 给参数x加双引号,即转换成字符串,注意:其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前
## 连接操作符 将两个参数按字面值连接成一个参数。注意:两个参数的类型要一致。
#@ 字符化操作符 给参数x加上单引号,即转换成字符
\ 行继续操作符 宏定义中,一行写不下时,利用反斜杠“\”进行换行,反斜杠后不能有空格

参考文献

宏定义中的#,##,#@,\符号用法

undef(终止宏作用域)

#undef 指令,用来终止 #define宏定义的作用域

  • 定义格式:#undef 标识符,标识符表示要终止作用域的宏。
// 哪里声明就在哪里终止 宏A 的作用域
#undef A

参考文献

宏定义(无参宏定义和带参宏定义),C语言宏定义详解

你可能感兴趣的:(C,语言基础,c语言,开发语言)