宏定义#define

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41071068/article/details/92805050
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
            <div class="htmledit_views" id="content_views">
                                        <h1 id="%E5%AE%8F%E5%AE%9A%E4%B9%89%23define%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93"><a name="t0"></a>宏定义#define的一些总结</h1>

  • 宏定义#define

#define 指令将 标识符 定义为宏,即指示编译器会将之后出现的所有 标识符 都替换为 替换列表,而它也可以被进一步处理

#表示这是一条预处理指令

在C/C++源代码中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。

#define的基本语法非常简单, 但完全不影响它的强大
不过有时候正因为宏(#define)的简单、方便和强大,就会导致我们平常在使用的时候,其中会有很多的注意点和细节需要我们去注意,如果不小心将其忽略, 那么可能会带来我们意料之外的不想要的结果。

推荐用宏但不能滥用, 比如在定义常量时
C中const定义的常量比较局限(不能作为定义数组的大小等) ,但在C++中, 常量完全可以用const, 而且宏不做检查,只是代码替换,而const会进行编译检查,const要比宏更安全。所以应尽可能的使用const来代替类对象宏。
链接: C++中举例说明可以使用const代替#define以消除#define的不安全性


目录

类对象宏(无参宏)
类函数宏(带参数的宏)
#的作用
##的作用
类函数宏(带参数的宏)和函数的对比
#undef
防止头文件被重复包含或引用


类对象宏(无参宏)

类对象宏(无参宏), 即宏名之后不带参数,只是简单的文本替换

其定义的一般形式为:

#define 标识符 字符串       

下面来看段代码


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define 整型 int
  4. #define N 10
  5. #define str "哈哈哈"
  6. #define D1 double*
  7. typedef double* D2;
  8. typedef char 字符型;
  9. int main() {
  10. 整型 num = 5;
  11. printf( "num = %d\n%d\n", num, sizeof(整型));
  12. printf( "N = %d\n", N);
  13. printf( "str :%s\n", str);
  14. //double* a1, b1;
  15. D1 a1, b1;
  16. D2 a2, b2;
  17. 字符型 ch = 'a';
  18. printf( "ch = %c\n", ch);
  19. printf( "%d %d\n", sizeof(a1), sizeof(b1));
  20. printf( "%d %d\n", sizeof(a2), sizeof(b2));
  21. system( "pause");
  22. return 0;
  23. }

 

我们可以看到, 宏是在进行着文本替换(预处理阶段)

这里要注意一下typedef#define的区别, 在程序运行过程中我们发现, #define只是进行了文本替换
D1 a1, b1;语句与double* a1, b1无异, a1为double* 类型, b1为double类型, 要都为指针类型, 应double *a1,*b1;定义或分开定义
double* a1;double* b1;
typedef double* D2; 语句将D2作为double* 类型的别名, 所以a2, b2这两个变量都是double* 类型, 在给变量类型起别名时尽量用typedef


  • 类函数宏(带参数的宏)

C/C++允许宏带有参数。

带参数的宏定义,宏名中不能有空格,宏名与形参表之间也不能有空格,而形参表中形参之间可以有空格, 在行末不必加分号,如加上分号则在预处理时连分号也一起添加到代码中。

约定 : 一般来说函数和类函数宏使用语法很相近, 所以语言本身无法帮我们区分二者, 那我们平时的一个习惯是:
          把宏名全部大写 函数名不要全部大写
 

 直接来看一段代码

代码1:

 


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define M1(a,b) a*b
  4. #define M2(a,b) (a)*(b)
  5. #define M3(a,b) ((a)*(b))
  6. #define A1(a,b) a+b
  7. #define A2(a,b) (a)+(b)
  8. #define A3(a,b) ((a)+(b))
  9. int main() {
  10. int i = 4;
  11. int j = 5;
  12. printf( "%d\t%d\t%d\n", M1(i, j), M2(i, j), M3(i, j)); //结果20,20,20
  13. printf( "%d\n", M1(i + i, j + j)); //预期结果80,实际处理为i+i*j+j=4+20+5=29
  14. printf( "%d\n", M2(i + i, j + j)); //预期结果80,实际处理为(i+i)*(j+j)*(i+i)*(j+j)=80
  15. printf( "%d\n", M3(i + i, j + j)); //预期结果80,实际处理为((i+i)*(j+j)*(i+i)*(j+j))=80
  16. printf( "%d\t%d\t%d\n", A1(i, j), A2(i, j), A3(i, j)); //结果9,9,9
  17. printf( "%d\n", A1(i, j)*A1(i, j)); //预期结果9*9=81,实际处理为i+j*i+j=29
  18. printf( "%d\n", A2(i, j)*A2(i, j)); //预期结果9*9=81,实际处理为(i)+(j)*(i)+(j)=29
  19. printf( "%d\n", A3(i, j)*A3(i, j)); //预期结果9*9=81,实际处理为((i)+(j))*((i)+(j))=81
  20. system( "pause");
  21. return 0;
  22. }

 我们发现, 类函数宏也是在预编译阶段进行着简单的替换 , 所以会出现M1(i + i, j + j)结果为29, 是因为编译器在预处理阶段将M1(i + i, j + j)替换成了i + i * j + j 即4 + 4 * 5 + 5 ;真是好粗暴的的文本(代码)替换, 同样A2A3也存在这样的问题, 为了避免这样的错误产生, 我们可以像M3, A3那样处理, 就是给每个参数加括号, 再整体加括号, 就不会出现替换后运算级的问题了, 这种处理方法也最保险 . 

类函数宏还可以定义多个语句

注意: 如果宏定义太长, 可以分成几行书写, 除了最后一行外, 其余每行都在末尾加上 \ (反斜杠)(续行符)

看这段代码
代码2:


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define M(a,b,c,d) a=c+d;b=c-d
  4. #define PRINT_1(a,b) \
  5. printf(#a"+"#b"=%d\n",(a)+(b))
  6. #define SUM_PRINT(x,y,z) x##y += z;\
  7. printf(#x#y"=%d\n",x##y)
  8. int main() {
  9. int a, b, c, d, ab;
  10. c = 6;
  11. d = 2;
  12. ab = 0;
  13. M(a, b, c, d);
  14. printf( "%d %d %d %d\n", a, b, c, d);
  15. PRINT_1(a, b);
  16. SUM_PRINT(a, b, 5);
  17. system( "pause");
  18. return 0;
  19. }

 

 代码中用到了#与##, 分别是啥意思呢

  • #的作用

          把一个宏参数变成对应的字符串

  • ##的作用

          ##可以把位于##两边的字符(串)合成一个符号, 这样的操作必须产生一个合法的标识符,否则是无意义的, 结果是未定义的,      就如上面的代码中a和b(a##b)形成了新的标识符ab, ab是我们事先定义的, 所以有意义且正确


  •  类函数宏(带参数的宏)和函数的对比

类函数宏通常用于一些简单的运算, 比如说在两个数中找出最大或最小的一个

#define MAX(a,b) ((a)>(b)?(a):(b))
  
  

那为什么不用函数来完成这个任务?
原因有二:
1. 调用函数(需要开辟内存,释放内存等)可能比执行这个带参数的宏(插入一小段代码)进行小型计算所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于大小比较的类型。宏是类型无关的。

当然和宏相比函数也有劣势的地方:
1. 每次使用宏的时候,一个宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是不方便调试的。 
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候还可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
什么意思呢, 举个例子:


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define MALLOC(num, type)\
  4. (type *)malloc(num * sizeof(type))
  5. int main() {
  6. int* p;
  7. p = MALLOC( 10, int); //类型作为参数
  8. for ( int i = 0; i < 10; ++i) {
  9. *(p + i) = i + 1;
  10. }
  11. for ( int i = 0; i < 10; ++i) {
  12. printf( "%d ", *(p + i) = i + 1);
  13. }
  14. printf( "\n");
  15. system( "pause");
  16. return 0;
  17. }

宏真的是太神奇了, 在刚刚学习到这些内容时, 感觉无所不能啊

刚看完一个宏让函数无法望其项背的优点, 那再来看一个函数让宏可望不可即的地方
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,
导致不可预测的后果。例如:


  
  
  1. x+ 1; //不带副作用
  2. x++; //带有副作用

直接来看一段代码


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define MAX(a,b) ((a)>(b)?(a):(b))
  4. int mymax(int a, int b) {
  5. return a > b ? a : b;
  6. }
  7. int main() {
  8. int x1, y1, z1;
  9. int x2, y2, z2;
  10. x1 = 4;
  11. y1 = 5;
  12. z1 = MAX(x1++, y1++);
  13. x2 = 4;
  14. y2 = 5;
  15. z2 = mymax(x2++, y2++);
  16. printf( "x1=%d, y1=%d, z1=%d\n", x1, y1, z1);
  17. printf( "x2=%d, y2=%d, z2=%d\n", x2, y2, z2);
  18. system( "pause");
  19. return 0;
  20. }

我们发现函数运算结果与我们预期一样, 但宏出现的错误, 
原因是MAX进行了(x1++)>(y1++)?(x1++):(y1++) 即 4 > 5 ? 5 : 6 , 之后又将6(y1++)的值赋给z1, 所以z1等于6, 但y1++又在赋值过程中执行了一遍, 所以结果为7

 类函数宏(带参数的宏)和函数的对比总结

属性 #define宏定义 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长, 重复使用时代码会重复增多 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码,当重复使用时代码不会重复增多
执行速度 直接执行,更快 存在函数的的调用和返回的开销,所以相对慢一点
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 函数参数只在函数调用传参时求一次值, 会将这个值传给实参, 函数的执行结果更容易预测
带有副作用的参数 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果 函数参数只在函数调用传参时求一次值, ,运行结果更容易控制
参数类型 1.宏的参数与类型无关,只要对参数的操作是合法的,就可以使用任何类型可以操作的参数
2.可以是只是类型(如 int)
1.函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数(C中为不同名的函数, C++中可以函数重载,可以重名),即使他们执行的任务是不同的。
2.不能只是类型
调试 不方便调试 函数是可以逐语句调试的
递归 不能
  • #undef

这条指令用于移除一个宏定义。

#undef NAME
如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
直接上代码:


  
  
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #define M 10
  4. int main() {
  5. printf( "%d\n", M);
  6. #undef M
  7. #define M "#undef用于移除一个宏定义"
  8. printf( "%s\n", M);
  9. system( "pause");
  10. return 0;
  11. }


扩展

防止头文件被重复包含或引用

#include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:

#include "x.h"
#include "x.h"
显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include "a.h"
#include "b.h"
看上去没什么问题。如果a.h和b.h中都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。
解决方法有如下 : 

  • #pragma once 

只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

#pragma once 

  
  
  •  条件编译

我们可以使用条件编译。头文件都像下面这样编写: 


  
  
  1. #ifndef _HEADERNAME_H
  2. #define _HEADERNAME_H
  3. ... //(头文件内容)
  4. #endif

那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。

但是,必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。

发布了8 篇原创文章 · 获赞 3 · 访问量 1923
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41071068/article/details/92805050
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-d284373521.css">
            <div class="htmledit_views" id="content_views">
                                        <h1 id="%E5%AE%8F%E5%AE%9A%E4%B9%89%23define%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93"><a name="t0"></a>宏定义#define的一些总结</h1>

猜你喜欢

转载自blog.csdn.net/weixin_44017727/article/details/103089223