注册 登录
查看: 2204|回复: 108

关于 带参宏定义 与 自定义函数 的 汇总

  [复制链接]
发表于 2014-8-13 16:35:05 | 显示全部楼层 |阅读模式
本帖最后由 ii童话Bū说话 于 2014-8-13 17:20 编辑

山外哥的测试题在5楼!自己很菜!听了大神们的解释才懂了!
此处附上答案!
游客,如果您要查看本帖隐藏内容请回复
  1. ///////////////////////////////////////////////////////////////////////////////////////////////////
先把总结扔上:
游客,如果您要查看本帖隐藏内容请回复




以下内容摘自百度文档等各处,出处不祥!仅对各个内容作了些汇总!
  1. ////////////////////////////////////////////////////////////////////////////////////////////////////
普及
c语言 中 反斜杠( \)的作用:语义上表示,下一行是上一行的延续。也就是同一行。
当你的代码一行写的时候会太长,需要分行方便显示时,但代码又不能分行时,例如这里的宏定义,只能在一行定义好,那样就可以用过在结尾添加 反斜杠( \) 来换行。表示 接着下一行,就是例子中的整个 if-else 语句都被 反斜杠( \) 连接在同一行,所以替换后就仅仅一行而已。
注意,反斜杠( \) 后面不能有任何字符,包括空格。
例:
  1. #define LED1(a)   if (a)\
  2.                          GPIO_SetBits(GPIOC,GPIO_Pin_3);\
  3.                     else \
  4.                          GPIO_ResetBits(GPIOC,GPIO_Pin_3)
带参宏定义与普通函数
带参宏定义函数调用

处理时间

只在预编译时处理在程序运行时处理
操作内容制作简单的字符替换,不进行值的传递,也没有返回值和类型的概念先求表达式的值,然后进行形参实参结合 的数据传递,返回一个值,有类型的概念。


函数式宏定义:#define MAX(a,b) ((a)>(b)?(a)b))
普通函数     : MAX(a,b) { return a>b?a:b;}
(1)函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。
(2)调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。
  如果MAX是个普通函数,那么它的函数体return a > b ? a : b; 要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。
而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。
所以,使用函数式宏定义编译生成的目标文件会比较大。
(3)函数式宏定义要注意格式,尤其是括号。
  如果上面的函数式宏定义写成 #define MAX(a, b) (a>b?a:b),省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层括号也是不能省的。若函数中是宏替换为 ++MAX(a,b),则宏展开就成了 ++(a)>(b)?(a)b),运算优先级也是错了。
(4)若函数参数为表达式,则普通函数的调用与函数式宏定义的替换过程是不一样的。
  普通函数调用时先求实参表达式的值再传给形参,如果实参表达式有Side Effect,那么这些SideEffect只发生一次。例如MAX(++a, ++b),如果MAX是普通函数,a和b只增加一次。但如果MAX函数式宏定义,则要展开成k = ((++a)>(++b)?(++a)++b)),a和b就不一定是增加一次还是两次了。所以若参数是表达式,替换函数式宏定义时一定要仔细看好。
注:那么怎样安全的使用函数式宏定义呢?
把可能产生副作用的操作移到宏调用的外面进行
  1. a++;
  2. b++;
  3. k = ((a)>(b)?(a):(b));
(5)函数式宏定义往往会导致较低的代码执行效率。
  1. int a[]={9,3,5,2,1,0,8,7,6,4};
  2. int max(n)
  3. {
  4.     return n==0?a[0]:MAX(a[n],max(n-1));
  5. }

  6. int main()
  7. {
  8.     max(9);
  9.     return 0;
  10. }
若是普通函数,则通过递归,可取的最大值,时间复杂度为O(n)。但若是函数式宏定义,则宏展开为( a[n]>max(n-1)?a[n]:max(n-1) ),其中max(n-1)被调用了两遍,这样依此递归下去,时间复杂度会很高。
  尽管函数式宏定义和普通函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作。
因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。

宏定义

1.#define指令
#define预处理指令是用来定义宏的。该指令最简单的格式是:首先声明一个标识符,然后给出这个标识符代表的代码。在后面的源代码中,就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来,赋给一些记忆标识符。
  1. #defineMAX_NUM10

  2. intarray[MAX_NUM];

  3. for(i=0;i<MAX_NUM;i++)

  4. /*……*/
在这个例子中,对于阅读该程序的人来说,符号MAX_NUM就有特定的含义,它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定,习惯上总是全部用大写字母来定义宏,这样易于把程序宏的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小,只需要更改宏定义并重新编译程序即可。
宏表示的值可以是一个常量表达式,其中允许包括前面已经定义的宏标识符。
例如:
  1. #define ONE1

  2. #define TWO2

  3. #define
  4. THREE(ONE+TWO)
注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。
例如:
  1. six=THREE*TWO;
预处理过程把上面的一行代码转换成:
  1. six=(ONE+TWO)*TWO;
如果没有那个括号,就转换成six=ONE+TWO*TWO;了。
宏还可以代表一个字符串常量,
例如:
  1. #define VERSION "Version1.0Copyright(c)2003"


2.带参数的#define指令
带参数的宏和函数调用看起来有些相似。看一个例子:
#defineCube(x)(x)*(x)*(x)
可以时任何数字表达式甚至函数调用来代替参数x。这里再次提醒大家注意括号的使用。
宏展开后完全包含在一对括号中,而且参数也包含在括号中,这样就保证了宏和参数的完整性。
看一个用法:
  1. intnum=8+2;
  2. volume=Cube(num);
展开后为
(8+2)*(8+2)*(8+2);
如果没有那些括号就变为8+2*8+2*8+2了。
下面的用法是不安全的:
  1. volume=Cube(num++);
如果Cube是一个函数,上面的写法是可以理解的。但是,因为Cube是一个宏,所以会产生副作用。这里的参数不是简单的表达式,它们将产生意想不到的结果。
它们展开后是这样的:
  1. volume=(num++)*(num++)*(num++);
很显然,结果是10*11*12,而不是10*10*10;那么怎样安全的使用Cube宏呢?
必须把可能产生副作用的操作移到宏调用的外面进行
  1. int num=8+2;
  2. volume=Cube(num);
  3. num++;


3.#运算符
出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。例如:
  1. #definePASTE(n) "adhfkj"#n
  2. main()
  3. {
  4.     printf("%s\n",PASTE(15));
  5. }
宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是adhfkj15。


4.##运算符
##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。
看下面的例子:
  1. #defineNUM(a,b,c) a##b##c

  2. #defineSTR(a,b,c)
  3. a##b##c

  4. main()

  5. {

  6.     printf("%d\n",NUM(1,2,3));

  7.     printf("%s\n",STR("aa","bb","cc"));

  8. }
123
aabbcc
千万别担心,除非需要或者宏的用法恰好和手头的工作相关,否则很少有程序员会知道##运算符。绝大多数程序员从来没用过它。

宏定义防止 使用是错误
用小括号包含。
例如:#define ADD(a,b) (a+b)
用do{}while(0)语句包含多语句防止错误
例如:#difne DO(a,b) a+b;\
                   a++;
应用时:if(….)
                    DO(a,b); //产生错误
            else
                   ……
解决方法:
#difne DO(a,b) do{a+b;\
                   a++;}while(0)












本帖被以下淘专辑推荐:

回复

使用道具 举报

 楼主| 发表于 2014-8-13 16:35:26 | 显示全部楼层
条件编译指令
条件编译指令将决定那些代码被编译,而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”。(conditional compile)

  条件编译语句排版时,需考虑以下三种位置:

  (1)条件编译语句块与函数定义体之间不存在相互嵌套(主要在(.h)文件中)

  ◆ 条件编译关键字语句顶格左对齐;

  ◆ 所含的#include语句(块) #define语句(块)甚至是被嵌套下级条件编译语句块,按照语句块嵌套的排版方式进行缩进排版 。

  (2)条件编译语句块嵌套在函数体之外(主要在(.c)文件中)

  这种情况下,条件编译语句块不影响函数体

  ◆ 条件编译关键字语句顶格左对齐;

  ◆ 所含的函数体定义无需缩进,依旧按照单个函数体定义的排版方式进行。

  (3)条件编译语句嵌套在函数体内 (主要在(.c)文件中)

  a)当条件编译语句块与被包语句所属的语句块之间没有逻辑路径交叉时,以下两种方式均可

  ◆ 按照语句块嵌套方式进行缩进排版 (推荐);

  ◆ 条件编译语句不影响原先语句块排版,条件编译语句与所包含的关键字语句块左对齐 。

  b)当条件编译语句块与被包语句所属的语句块之间存在逻辑路径交叉时

  ◆ 条件编译语句顶格左对齐,其它语句按照正常顺序排版。


1.#if指令
#if指令检测跟在关键字后的常量表达式。如果表达式为真,则编译后面的代码,知道出现#else、#elif或#endif为止;否则就不编译。
条件编译的形式如下所示(NNN、MMM等都是在某处已经定义为 1 或者 0 的):
  1.   #if NNN
  2.   statement1;
  3.   #elif MMM
  4.   statement2;
  5.   #else
  6.   statement3;
  7.   #endif
重要解释:若宏NNN为True则只留下statement1编译;若NNN为False且MMM为True则只编译statement2;若NNN和MMM都为False则编译statement3。
#if是在编译前进行抉择的,而一般的if指令是在程序运行时才做抉择的,因此#if可以提升程序的执行速度,这是两者的重要区别。另外,#if指令还可协助查错。


2.#endif指令
#endif用于终止#if预处理指令。
  1. #defineDEBUG0
  2. main()
  3. {
  4.     #if DEBUG
  5.     printf("Debugging\n");
  6.     #endif
  7.     printf("Running\n");
  8. }
由于程序定义DEBUG宏代表0,所以#if条件为假,不编译后面的代码直到#endif,所以程序直接输出Running。
如果去掉#define语句,效果是一样的。


3.#ifdef和#ifndef
  1. #define DEBUG

  2. main()

  3. {

  4.     #ifdef DEBUG

  5.     printf("yes\n");

  6.     #endif

  7.     #ifndef DEBUG

  8.     printf("no\n");

  9.     #endif
  10. }
#ifdefined等价于#ifdef;
#if!defined等价于#ifndef
防止一个头文件被重复包含
  1. #ifndef COMDEF_H
  2. #define COMDEF_H  //头文件内容
  3. #endif
4.#else指令
#else指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码。
#endif指令将中指上面的条件块。

5.#elif指令
#elif预处理指令综合了#else和#if指令的作用。
  1. #define TWO
  2. main()
  3. {
  4.     #ifdef ONE
  5.     printf("1\n");
  6.     #elif defined TWO
  7.     printf("2\n");
  8.     #else
  9.     printf("3\n");
  10.     #endif
  11. }
程序很好理解,最后输出结果是2。

区分:
#ifdef 宏   //若已定义了此宏,则留下#ifdef与#endif间的指令;否则删除之。
#ifndef 宏 //若未定义过此宏,则留下#ifndef与#endif间的指令;否则删除之。
#endif //定义#ifdef及#ifndef的范围。
#undef 宏 //与#defined相反的动作---解除定义。
#else  //可构成#ifdef~#else~#endif结构或#ifndef~#else~#endif结构。
#ifdef与#if的区别
#if 宏:此宏必须已定义,依宏所代表的值来做判断;
#ifdef 宏:此宏不一定已定义,依此宏是否已定义来判断。

组合:
1:情况1:
#ifdef _XXXX
...程序段1...
#else
...程序段2...
#endif
这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。
例:
#define NUM
.............
.............
.............
#ifdef NUM
printf("之前NUM有过定义啦! ");
#else
printf("之前NUM没有过定义! ");
#endif
}
如果程序开头有#define NUM这行,即NUM有定义,碰到下面#ifdef NUM的时候,当然执行第一个printf。否则第二个printf将被执行。
我认为,用这种,可以很方便的开启/关闭整个程序的某项特定功能。
2:情况2:
#ifndef _XXXX
...程序段1...
#else
...程序段2...
#endif
这里使用了#ifndef,表示的是if not def。当然是和#ifdef相反的状况(如果没有定义了标识符_XXXX,那么执行程序段1,否则执行程序段2)。例子就不举了。
3:情况3:
#if 常量
...程序段1...
#else
...程序段2...
#endif
这里表示,如果常量为真(非0,随便什么数字,只要不是0),就执行程序段1,否则执行程序段2。
这种方法可以将测试代码加进来。当需要开启测试的时候,只要将常量变1就好了。而不要测试的时候,只要将常量变0。

6.其他一些标准指令
#error指令将使编译器显示一条错误信息,然后停止编译。
#line指令可以改变编译器用来指出警告和错误信息的文件号和行号。
#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。

关于宏定义的用法
移步http://www.vcan123.com/forum.php?mod=viewthread&tid=123&highlight=%BA%EA

回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-8-13 16:35:49 | 显示全部楼层
自己占个板凳
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-8-13 16:36:09 | 显示全部楼层
还有席梦思也占上!
回复 支持 反对

使用道具 举报

发表于 2014-8-13 16:40:41 | 显示全部楼层
给你出一个题目,看你会不会。
求打印的结果。不要上机验证结果,自己分析一下答案。
  1. typedef enum
  2. {
  3.     A = 0,
  4.     B = 1,
  5. }TTT;


  6. #if (B == 1)
  7.     printf("B=1");
  8. #elif (B == 0)
  9.     printf("B=0");
  10. #else
  11.     printf("error");
  12. #endif
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-8-13 17:21:44 | 显示全部楼层
山外メ雲ジ 发表于 2014-8-13 16:40
给你出一个题目,看你会不会。
求打印的结果。不要上机验证结果,自己分析一下答案。

感谢山外哥指导!膜拜啊!
回复 支持 反对

使用道具 举报

发表于 2014-9-6 23:53:48 | 显示全部楼层
看看 典型用法!   走过路过,不能错过
回复 支持 反对

使用道具 举报

发表于 2014-9-7 19:27:46 | 显示全部楼层
5楼的题目太容易错了
回复 支持 反对

使用道具 举报

发表于 2014-9-7 19:29:33 | 显示全部楼层
ii童话Bū说话 发表于 2014-8-13 17:21
感谢山外哥指导!膜拜啊!

楼主搞得很透啊
回复 支持 反对

使用道具 举报

发表于 2014-9-28 19:08:22 | 显示全部楼层
B=1
回复 支持 反对

使用道具 举报

发表于 2014-9-28 19:09:42 | 显示全部楼层

不对,看楼主贴解释。
回复 支持 反对

使用道具 举报

发表于 2014-9-28 19:11:13 | 显示全部楼层
恩恩,拜读了,多谢山外哥,多谢楼主
回复 支持 反对

使用道具 举报

发表于 2014-9-28 19:13:34 | 显示全部楼层
MJJ 发表于 2014-9-28 19:11
恩恩,拜读了,多谢山外哥,多谢楼主

我在项目开发中,被这问题坑过,害惨了。所以把他整理为题目,希望其他人不要再犯这错误。
回复 支持 反对

使用道具 举报

发表于 2014-9-28 19:17:12 | 显示全部楼层
山外メ雲ジ 发表于 2014-9-28 19:13
我在项目开发中,被这问题坑过,害惨了。所以把他整理为题目,希望其他人不要再犯这错误。

恩恩,这样我们在编写程序的时候就少了一个犯错误的可能了,真心感谢山外哥,感谢山外论坛!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-10-8 13:08:07 | 显示全部楼层
许亮 发表于 2014-9-7 19:29
楼主搞得很透啊

走的慢一些!搞得透一些!免得以后走弯路!那时候更头疼!已经吃过亏了!都是血淋淋的教训啊!
回复 支持 反对

使用道具 举报

发表于 2014-10-8 13:40:18 | 显示全部楼层
ii童话Bū说话 发表于 2014-10-8 13:08
走的慢一些!搞得透一些!免得以后走弯路!那时候更头疼!已经吃过亏了!都是血淋淋的教训啊!

基础好,才可以跑得快。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-10-8 15:22:00 | 显示全部楼层
山外メ雲ジ 发表于 2014-10-8 13:40
基础好,才可以跑得快。

恩恩!前期我会慢一点的!





回复 支持 反对

使用道具 举报

发表于 2014-10-12 11:41:19 | 显示全部楼层
好多大神哦~大一的新手来学习技术了~求前辈指导
回复 支持 反对

使用道具 举报

发表于 2014-10-12 13:15:32 | 显示全部楼层
山鹰/SamFisher 发表于 2014-10-12 11:41
好多大神哦~大一的新手来学习技术了~求前辈指导

多在每日一题里练习一下,很快就成为大神。
回复 支持 反对

使用道具 举报

发表于 2014-10-23 21:03:52 | 显示全部楼层
回复了
看看吧
回复 支持 反对

使用道具 举报

发表于 2014-10-24 12:24:13 | 显示全部楼层
回复看一下。
回复 支持 反对

使用道具 举报

发表于 2014-10-25 22:02:37 | 显示全部楼层
山外メ雲ジ 发表于 2014-8-13 16:40
给你出一个题目,看你会不会。
求打印的结果。不要上机验证结果,自己分析一下答案。

运行结果应该是:B=1
回复 支持 反对

使用道具 举报

发表于 2014-11-4 10:02:49 | 显示全部楼层
BBBBBBBBBBBBB
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-11-4 16:18:35 | 显示全部楼层

BBBBBBBB=?
回复 支持 反对

使用道具 举报

发表于 2014-11-12 17:53:27 | 显示全部楼层
回个帖子现在还要字数限制
回复 支持 反对

使用道具 举报

发表于 2014-12-10 11:31:43 | 显示全部楼层
还有很多东西要学,马克了
回复 支持 反对

使用道具 举报

发表于 2014-12-13 21:37:52 | 显示全部楼层
..................................
回复 支持 反对

使用道具 举报

发表于 2014-12-25 13:09:04 | 显示全部楼层
                                                         
回复 支持 反对

使用道具 举报

发表于 2015-1-9 20:29:25 | 显示全部楼层
好资料要分享!
回复 支持 反对

使用道具 举报

发表于 2015-1-14 20:05:12 | 显示全部楼层
难道不是B=1么
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-1-15 20:17:10 | 显示全部楼层
郭筱筱 发表于 1421237112
难道不是B=1么
当然!不是!
来自PC客户端 来自PC客户端
回复 支持 反对

使用道具 举报

发表于 2015-1-26 23:22:06 | 显示全部楼层
新人学习学习
回复 支持 反对

使用道具 举报

发表于 2015-1-27 01:48:06 | 显示全部楼层
快到碗里来
回复 支持 反对

使用道具 举报

发表于 2015-1-28 10:34:32 | 显示全部楼层
会斤斤计较
回复 支持 反对

使用道具 举报

发表于 2015-1-28 20:11:49 | 显示全部楼层
好好哦
回复 支持 反对

使用道具 举报

发表于 2015-1-31 00:07:15 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2015-2-2 10:34:53 | 显示全部楼层
学习了
回复 支持 反对

使用道具 举报

发表于 2015-3-29 20:46:37 | 显示全部楼层
学习中
回复 支持 反对

使用道具 举报

发表于 2015-4-4 15:16:47 | 显示全部楼层

回复 支持 反对

使用道具 举报

发表于 2015-4-7 16:10:32 | 显示全部楼层
不会。
回复 支持 反对

使用道具 举报

发表于 2015-4-12 14:35:16 | 显示全部楼层
先看看,目前很痛苦

回复 支持 反对

使用道具 举报

发表于 2015-4-14 14:48:14 | 显示全部楼层
学习
回复 支持 反对

使用道具 举报

发表于 2015-4-16 15:24:52 | 显示全部楼层
学习啦
回复 支持 反对

使用道具 举报

发表于 2015-4-22 09:19:59 | 显示全部楼层
大神
回复 支持 反对

使用道具 举报

发表于 2015-5-10 14:59:35 | 显示全部楼层
先看看,学习下。
回复 支持 反对

使用道具 举报

发表于 2015-5-16 22:17:41 | 显示全部楼层
学习
回复 支持 反对

使用道具 举报

发表于 2015-5-23 23:40:12 | 显示全部楼层
学习学习。。谢谢。。。。。
回复 支持 反对

使用道具 举报

发表于 2015-6-7 21:31:29 | 显示全部楼层
学习~
回复 支持 反对

使用道具 举报

发表于 2015-6-9 20:43:10 | 显示全部楼层
BIXU XUEXI A A A
回复 支持 反对

使用道具 举报

发表于 2015-6-18 21:33:45 | 显示全部楼层
神秘!

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回列表 返回顶部