在C语言中,我们使用#define来定义宏。在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本。

不带参数的宏

关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形式:

1#define 宏名称    替换文本

例如:

1#define ARRAY_SIZE    10
2int data[ARRAY_SIZE];

当程序需要修改数组长度时,只需要修改宏定义即可,无需对程序中每一处用到数组长度的地方进行修改。

带参数的宏

你可以定义具有形参的宏,然后预处理器展开这类宏时,会将宏指定的实参替换文本中对应的形参。这有点像函数,故也叫做函数式宏定义、类函数宏,形式如下:

1#define 宏名称([行参列表]) 替换文本
2#define 宏名称([行参列表,]...)    替换文本

当宏被调用时,替换文本中的每个值都与形参列表相对应。另外C99标准允许定义有参略号的宏,省略号必须放在参数列表的后面,以表示可选参数。

当调用有可选参数的宏时,预处理器会将所有可选参数连同分隔它们的逗号打包在一起作为一个参数。在替换文本中,标识符 __VA_ARGS__对应前面打包的可选参数。

1//假设有个已经打开的日志文件,准备采用文件指针fp_log对其进行写入
2#define printLog(...) fprintf(fp_log, __VA_ARGS__)
3//使用printLog
4printLog("%s: intVar=%d\r\n", __func__, intVar);

预处理器把最后一行的宏调用替换成下面一行代码:

1fprintf(fp_log, "%s: intVar=%d\r\n", __func__, intVar);

带参宏的一些问题

1#include <stdio.h>
2#define SQUARE(x)   x * x
3int main(int argc, char const *argv[])
4{
5    int a = 5;
6    printf("%d\r\n", SQUARE(5));
7    printf("%d\r\n", SQUARE(a+1));
8    return 0;
9}

程序运行,第一条打印显而易见为25,第二条打印为多少呢?不是36而是11。我们通过"gcc -E a.c >a.txt",将预处理后的文件重定向到a.txt,来观察被替换的宏文本。

1printf("%d\r\n", 5 * 5);
2printf("%d\r\n", a+1 * a+1);

可见替换产生的表达式并没有按照预想的次序进行求值。可能大家会说,加上两个括号就解决了嘛:

1#define SQUARE(x)  (x) * (x)

那么,为每个出现在替换文本中的参数加上括号就一定没问题了吗?看下面例子:

1#include <stdio.h>
2#define ADD(x)    (x) + (x)
3int main(int argc, char const *argv[])
4{
5    int a = 5;
6    printf("%d\r\n", 10 * ADD(5));
7    return 0;
8}

看似输出100,实际输出55。我们看下替换后的文本:

1printf("%d\r\n", 10 * (5) + (5));

乘法运算在加法运算之前执行,所以结果为55。这个错误很容易修正:整个表达式加上括号:

1#define ADD(x)    ((x) + (x))

所有对数值表达式进行求值的宏定义,为避免参数中操作符或邻近的操作符之间不可预料的互相作用,应对每个参数加括号,整个表达式也要加括号。

宏与函数

尽可能多的加括号就绝对不会有问题了吗?看下面例子:

 1#include <stdio.h>2#define MAX(a, b)    ((a) > (b) ? (a) : (b))3int main(int argc, char const *argv[])4{5    int a = 5;6    int b = 5;7    int m = MAX(++a, b);8    printf("%d\r\n", m);9    return 0;
10}

m的值是多少?是不是相当于执行MAX(6, 5),然后输出6呢?答案是7,看下替换的文本:

1int m = ((++a) > (b) ? (++a) : (b));

这里就是C语言中函数式宏定义的陷阱,传递参数++a会被展开到替换文本的每一个a处。所以,再多的括号也不可能确保万无一失。

那么,函数式宏定义较函数有什么优点吗?

  • 相较于函数的分配和释放栈、传参、传返回值等一系列操作,函数式宏定义的代码执行效率高。

  • 函数的参数必须声明为一种特定的类型,而宏是与类型无关的。以比较大小为例,如果我们需要比较整形、长整型、浮点型数值大小,由于C语言不支持函数重载,我们需要为每一种数据类型实现一个max()函数,显然使用宏定义要来的方便了。当然宏定义不检测参数类型也是把双刃剑,可能导致程序不安全,需要特别注意。

  • 还有一些函数无法实现的任务,比如说,如何将数据类型作为参数传递给函数?看宏定义怎么破:

1#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
2...
3p = MALLOC(20, int);

替换后的文本:

1p = ((int *)malloc((20) * sizeof(int)));



你点的每个赞,我都当成喜欢

大牛深入浅出讲解C语言#define宏定义应用及使用方法相关推荐

  1. 深入浅出讲解C语言#define宏定义应用及使用方法

    在C语言中,我们使用#define来定义宏.在C程序编译的预处理阶段,预处理器会把宏定义的符号替换成指定的文本. 不带参数的宏 关于宏最常见的就是用来定义数值常量的名称,即没有参数的宏定义,采用如下形 ...

  2. 大牛深入浅出讲解c语言do{...}while(0)功能及用法

    当第一次遇到do{-}while(0),我是懵的,这是什么操作,为了好看吗?后来发现Linux内核中随处可见啊,大神们这样的操作肯定是有道理的.查询了一些资料,做一下总结.在今后C语言开发中,你也可以 ...

  3. 关于C语言define宏定义字符串常量

    1.问题由来: 本人一直以为宏对于字符串的处理也是直接在预处理时进行替换:但是最近在工作中遇到了字符串宏+1的情况:于是彻底的颠覆了以前的思维:于是乎进行测试验证得出以下结果. 2.测试代码 /*测试 ...

  4. C语言#define宏定义可能注意不到的地方

    #define使用的核心:直接替换 我也觉得自己很清楚这一点,但看到这一道输出程序片段结果题,还是懵了.大家也可以在不看我下方答案的情况下,自己做一下,题目如下: #include<stdio. ...

  5. c语言解除宏定义_C语言宏定义 define,及一些陷阱!

    https://m.toutiaocdn.com/group/6584292311289561607/?iid=39362926900&app=news_article&timesta ...

  6. c语言中宏定义的字符替换#define M(x,y,z) x*y+z

    C语言中宏定义的字符替换问题 例子: 在c语言中定义如下宏 #define M(x,y,z) x*y+z 给定如下程序 #include<stdio.h> #include<stdl ...

  7. 关于C语言刷题(#define宏定义函数的常见错误)

    关于C语言刷题(#define宏定义函数的常见错误) 首先我们来先看对#define的定义 define,宏定义,C语言中预处理命令一种.分为无参宏定义和带参宏定义.无参宏定义的一般形式为:#defi ...

  8. C语言基础知识之define宏定义表达式,undef,内存对齐,a和a的区别,数组知识点,int (*)[10] p,二维数组参数与二维指针参数,函数指针数组,常见的内存错误及对策

    一.用define宏定义表达式 1.定义一年有多少秒: #define SEC_A_YEAR 60*60*24*365 //上述描述不可靠,没有考虑到在16位系统下把这样一个数赋给整型变量的时候可能会 ...

  9. 【C语言】----宏定义,预处理宏

    什么是宏? 宏是学习任何语言所不可缺少的,优秀的宏定义可以使得代码变得很简洁且高效,有效地提高编程效率. 宏是一种预处理指令,它提供了一种机制,可以用来替换源代码中的字符串,解释器或编译器在遇到宏时会 ...

最新文章

  1. 人工智能:AI 芯片快速起航
  2. boost::spirit模块使用 phoenix 对逗号分隔的数字列表求和的解析器
  3. 《javascript 高级程序设计》 笔记1 1~7章
  4. linux网络编程之字节序
  5. jpa jsf_完整Web应用程序Tomcat JSF Primefaces JPA Hibernate –第2部分
  6. Latex+Texstudio+Texlive 2020 windows10 安装教程
  7. EF中报错:附加类型“xxxx”的实体失败,因为相同类型的其他实体已具有相同的主键值。
  8. ctf 改变图片高度_通过CRC32爆破修改图片的宽高 ctf-misc图片隐写
  9. mysql第四步安装失败_MySQL8.0版本的安装以及解决安装后MySQL服务无法启动的问题...
  10. 中科院回应木兰语言造假:当事人已停职;中国软件业务收入百强:华为蝉联十八冠;Ionic 5.0.0-beta.5 发布|极客头条...
  11. pandas -读取文件时,加入列索引
  12. 双机热备、双机互备与 双机双工的区别
  13. 打字练习网站keybr.com
  14. 我的非计算机科班好友,斩获了十几个 offer
  15. 文件 或者 图片 与 base64 之间的转换
  16. vue集成svg-sprite-loader
  17. java实现花呗分3、6、12期计算用户每期手续费及每期总费用
  18. 算法创作|随机出10道题并计算正确率问题解决方法
  19. Java行业2019年的发展前景
  20. 讨厌程序员_我是程序员但讨厌编程

热门文章

  1. BringWindowToTop(), SetForegroundWindow(), SetActiveWindow()
  2. Redis简介 与Memcache的区别
  3. Git系列之(七) 常用指令 git reset
  4. get请求的乱码解决方式
  5. UA MATH564 概率论 QE练习 Glivenko–Cantelli定理
  6. 初步了解WPF依赖属性
  7. VC6生成随机浮点数、C++11的random头文件以及Dev C++支持C++11
  8. C++设计模式实例图解
  9. C# 线程池和编程实例
  10. C++类型转换总结【转】