宏定义的使用

宏定义是什么
进入这里说明已经对宏定义的用途有所了解,顾名思义就是给某一个项东西重新定义一个名字。然后在我们在使用这项东西的时候可以用新定义的名字来替换。
为什么使用宏定义
我直接用原来的东西不就可以了?举个简单的例子。在一个数学计算的程序中,我们可能很多处用到一个圆周率,我们可以写成3.14。有一天程序因需求要把圆周率精确到小数点后四位也就是3.1416.如果有10处用到了圆周率,我们就需要改10处,那如果有100处?1000处呢?这时候就需要用到宏定义了,我们可以定义一个M_PI来代表圆周率,以后在程序中所有用到圆周率的地方我们都可以用M_PI来代替,当需要修改的时候,只需要修改一处M_PI替代的值就可以完成需求。是不是很方便呢?
宏定义可分为不带参数的宏和带参数的宏
以#define来开头

不带参数的宏
#define M_PI 3.14
#define+空格+自定义名称+空格+要替代的内容
类似这样的 #define X A 的宏是比较简单的,在编译时编译器会在语义分析认定是宏后,将X替换为A,这个过程称为宏的展开。
我们以后在用到圆周率的时候就可以用M_PI代替,这样的宏一般都很简单,现在来看一下另外一种宏--带参数的宏

带参数的宏
顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如下面这个函数宏
#define add(A, B) A + B
在程序运行的过程中如果看到 add字样并且后面带括号,并且括号里面的参数与宏定义中的个数相符,那么系统会把相应的参数带入宏定义的参数,然后替换后面的内容,比如程序中有一个 add(3, 4) 的代码,系统会自动把3当做A,把4当做B,然后再替换成 3 + 4

这就是带参的宏的一个简单实用,好了宏定义已经学会了,是不是很简单呢? 是的!就这么简单,但是我们要牢记宏定义这个原理,还以add(3, 4)为题,宏在程序运行的过程中是宏展开!把add(3, 4)替换成了3 + 4而已,而不是像函数一样直接返回一个结果7, 这样我们就不得不考虑它的安全性,什么意思呢?以一个简单的小例子来说下吧。

宏的安全性
练习我们就先来定义一个2个数的最小宏吧!聪明的你一定很快就写出来了
#define MIN(A, B) A > B ? B : A
最小值宏定义V1.0发布~ 我们来使用一下 
printf("%d\n", MIN(3, 4));   运行成功打印了结果 3 
简直是太棒了

但是用过一段时间后我们很快就发现了问题
什么?粗BUG了?怎么回事?
原来你的同事在程序中这样用到了你的宏
问题1:
printf("%d\n", 3 * MIN(3, 4));

我们的本意用3乘以( 3和4中较小的一个数) 即3*3
打印结果, 什么?结果只是4?怎么会这样? 难道...难道...
于是我们会想宏定义的特点,哦,他只是简单的替换
那么我们就可以理解到编译器遇到 3 * MIN(3, 4) 会把宏简单的替换成 3 * 3 > 4 ? 4 : 3 是不是简单的替换?
这样我们就明白了其中的原理,3 * MIN(3, 4) 等价于 9 > 4 ? 4 : 3我们自然而然的就得到了4
怎么解决这个问题?简单!只需要加一个括号就行了
#define MIN(A, B) (A > B ? B : A)
最小值宏定义V2.0发布~ 这下就没问题了吧

很快你的同事又找到了你告诉你,你的宏出问题了
怎么回事???!!!
来到他电脑桌前看到了这么一行代码
问题2:
printf("%d\n", MIN(3, 4 > 5 ? 4 : 5));
我去,这小子想干嘛?一问之下才知道,原来他想比较4和5中比较大的一个数,然后在跟3比较哪一个小,又因为你没有给人家定义最大值的宏,所以只好自己写,运行之后得到了个5,怎么会这样?
老办法,展开宏定义吧  MIN(3, 4 > 5 ? 4 : 5) 展开后 (3 > 4 > 5 ? 4 : 5 ? 4 > 5 ? 4 : 5 : 3)
天啊!这是什么,经过一番梳理我们可以得到了结果 5
这和预想的完全不一样,可以看得出当一个算式套进去的时候优先级除了问题老方法,接着加括号吧
我们就老老实实的发布了最小值宏定义V3.0
#define MIN(A, B) ((A) > (B) ? (B) : (A)) 
这下终于得到了预期效果,从此以后这个“安全”的宏定义 再也没给你找过麻烦,直到有一天.......

同事:这个宏定义不行啊,到现在还是错误的
怎么可能!! 让我看下怎么用的
问题3:
    int a = 3;
    int b = 4;
    printf("%d", MIN(++a, ++b));
得到的结果怎么又是5
展开后我们发现是这个样子的  ((++a) > (++b) ? (++b) : (++a)) 
原来在这个表达式里面运行了2次  这还怎么办啊 
这时候就需要用到GNU C的赋值扩展,即使({...})的形式, 这种形式的语句可以类似很多脚本语言,在顺次执行之后,会将最后一次的表达式的赋值作为返回举个简单地小例子

int c = ({

int a = 5;

int b = 6;

b + a;

});

我们可以看到整个({..})里面最后一个表达式为 b+a 那么 b+a 就是这个块里整体的返回值,最后我们用c来接收了这个值, 那么c就是11.

那么我们就可以在宏定义的时候用这种形式,来返回最小是, 同时在这个块里面我们可以新定义变量来获取++a,++b的值,来保证他们只被执行一次

我们就可以这么写

#define MIN(A, B) ({__typeof__(A) __a = (A); __typeof__(B) __b = (B); __a > __b ? __b : __a;})

哇,这么一大坨是什么东西?我们来拆分下

#define MIN(A, B) ({\

__typeof__(A) __a = (A);\

__typeof__(B) __b = (B);\

__a > __b ? __b : __a;\

})                   (宏定义的时候是不可以直接换行的,我们可以在回车前面加 “\” 这个符号来让其换行)

这样是不是就清楚一些呢  __typeof__(A) 意思是取的A的类型。以MIN(++a, ++b)来说,因为我们要重新定义变量来接收a的值, 防止++a多次运行,那么定义变量之前我们要先知道这个a的类型,他可能是int 也可能是 float, 我们不会提前知道,所以用__typeof__(A)来取得他的类型,__typeof__()可以理解为取括号里面的变量的类型

那么就可以知道第一句 大致意思为   int __a = ++a;  同理 int __b = ++b; 最后比较新定义的变量__a > __b ? __b : __a; 同时它也是最后一个表达式,值会返回。这样就定义了一个天衣无缝的最小值宏定义了

最小值宏定义V4.0

#define MIN(A, B) ({__typeof__(A) __a = (A); __typeof__(B) __b = (B); __a > __b ? __b : __a;})

真的天衣无缝了么 我们可以先来看一下苹果的官方定义

#define __NSX_PASTE__(A,B) A##B

#if !defined(MIN)

#define __NSMIN_IMPL__(A,B,L) ({\

__typeof__(A) __NSX_PASTE__(__a,L) = (A);\

__typeof__(B) __NSX_PASTE__(__b,L) = (B);\

(__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); \

})

#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)

#endif

简单来说这个是为了解决我4.0中在({...})中可能会出现两个__a,导致重定义变量而引发未知错误,首先__NSX_PASTE__(A,B)是用来链接A B两个符号的,在__NSMIN_IMPL__又在定义变量名的时候用了__NSX_PASTE__(A,B)我们可以看到调用宏__NSX_PASTE__(__a,L)的时候传的参数用的时L 而L又来自__NSMIN_IMPL__(A,B,__COUNTER__)的__COUNTER__
,__COUNTER__系统系自动帮我们生成不重复的数字,1,2,3,4....依次生成这样在块中我们声明的变量就是变成 __a1, 再有的话就会生成__a2。避免了重命名的风险。

看明白了上面,对于宏定义应该已经入门了 入门? 是的,其实宏定义本身很简单,就是宏展开,完全替换掉我们定义的宏名,难点在于我们怎么预防宏展开后所引发的各种未知错误。正常的情况下多加些括号就完全可以搞定了

----------尾言:
           写了一上午,终于写完了。有错误的地方欢迎指正
           思考:真的多加括号就对了么?

时间: 2024-09-29 19:27:02

宏定义的使用的相关文章

宏定义中的#,##,...,do{}while(0),__VA_ARGS__

宏定义中的#,## 1.在一个预处理器宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组 #define syslog(a) fprintf(stderr,"Warning: " #a"\n"); 2.简单的说,"## "是一种分隔连接方式,它的作用是先分隔,然后进行强制连接 举列 -- 试比较下述几个宏定义的区别 #define A1(name, type)  type name_##type##_type 或 #define A

笔记3:预处理器-(2)宏定义

#define指令称为宏定义指令,通常用#define指令来定义一个宏用来代表其他东西的一个名字(如常量表达式等).通常来说预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令.当这个宏在后面的程序中使用到时,预处理器会"扩展"宏,将宏替换为其定义值. 简单的宏 简单的宏的定义格式: #define 标识符 替换列表 如: #define DTE_LEN 80 #define TRUE 1 #define FALSE 0 #define PI 3.1415926 #de

宏定义中使用do{}while(0)的好处 (转载)

宏定义中使用do{}while(0)的好处   #define MACRO_NAME(para) do{macro content}while(0) 的格式,总结了以下几个原因: 1,空的宏定义避免warning: #define foo() do{}while(0) 2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现. 3,如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现: #define foo(x) /action1(); /action2(); 在以下情况

Android.mk宏定义demo【转】

本文转载自:http://blog.csdn.net/u010164190/article/details/72783963 1.Android.mk  LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := binder_demo_showLOCAL_MODULE_TAGS := optionalLOCAL_SRC_FILES := test.cpp TARGET_BUILD_VARINT := userdebug #宏

c++编译时打印宏定义

#pragma message("this is message") #pragma message只能打印字符串,如果想打印任何宏定义可使用: #define PRINT_MACRO_HELPER(x) #x #define PRINT_MACRO(x) #x"="PRINT_MACRO_HELPER(x) #pragma message(PRINT_MACRO(var)) 如:#define __cplusplus 199911L #pragma message

typedef与define宏定义用于声明新的类型之间的区别

摘自<c专家编程> typedef可以看成一种彻底的封装类型,在typedef声明类型之后不能再往里面增加其他的内容. 例子: #define peach int unsigned peach i; //没问题 typedef int banana; unsigned banana i; //错误,不能增加unsigned #define宏定义只是用于简单的替换 #define int_ptr int * int_ptr chalk, cheese; int * chalk, cheese;

c 预处理的宏定义

概念 以“#”号开头的都是预处理命令 例如 #include <stdio.h>宏定义 宏定义无参数的宏名后不带参数# 表示这是一条预处理命令, define 为宏定义命令.“标识符”为所定义的宏名.“字符串”可以是常数,表达式.格式串等 举例: #define PI 3.1415926 作用: 就是用指定标识符PI来代替数3.1415926 对源程序作编译时, 将先由预处理程序进行宏代换, 即用3.1415926表达式去置换所有的宏名PI, 然后编译 #include <stdio.h

C语言学习——宏定义

1.简单的宏定义 #define <宏名> <字符串> 例:#define LEN 0.5 2.带参数的宏定义 #define <宏名> (<参数表>) <宏体>例:#define Fun(para) para*2 3.实例分析 1 #define VALUE 2+2 2 void main() 3 { 4 int n=VALUE*VALUE; 5 printf("%d",n); 6 } 这段代码的输出是多少呢?16?也许会让

宏定义与内联函数

1.宏定义的规则和使用解析(1)宏定义的解析规则就是:在预处理阶段由预处理器进行替换,这个替换是原封不动的替换.(2)宏定义替换会递归进行,直到替换出来的值本身不再是一个宏为止.(3)一个正确的宏定义式子本身分为3部分:第一部分是#dedine ,第二部分是宏名 ,剩下的所有为第三部分.(4)宏可以带参数,称为带参宏.带参宏的使用和带参函数非常像,但是使用上有一些差异.在定义带参宏时,每一个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可. 宏定义示例1:MAX宏,求2个数中较大的

【转】C语言中DEFINE简介及多行宏定义

要写好C语言,漂亮的宏定义是非常重要的.宏定义可以帮助我们防止出错,提高代码的可移植性和可读性等. 在软件开发过程中,经常有一些常用或者通用的功能或者代码段,这些功能既可以写成函数,也可以封装成为宏定义.那么究竟是用函数好,还是宏定义好?这就要求我们对二者进行合理的取舍. 我们来看一个例子,比较两个数或者表达式大小,首先我们把它写成宏定义: #define MAX( a, b) ( (a) > (b) (a) : (b) ) 其次,把它用函数来实现: int max( int a, int b)