宏 简介

(转自:详解宏定义

概念及无参宏

一种最简单的宏的形式如下:

#define   标识符   替换文本

每个 #define行 (即逻辑行) 由三部分组成:第一部分是指令 #define 自身,“#” 表示这是一条预处理命令,“define” 为宏命令。第二部分为(macro),一般为缩略语,其名称(宏名) 一般大写,而且不能有空格,遵循C变量命令规则。“替换文本” 可以是任意常数、表达式、字符串等。在预处理工作过程中,代码中所有出现的“宏名”,都会被 “替换文本” 替换。这个替换的过程被称为 “宏代换”“宏展开”(macro expansion)。“宏代换” 是由预处理程序自动完成的。在C语言中,“宏” 分为两种:无参宏有参宏

无参宏 是指宏名之后不带参数,上面最简单的宏就是无参宏。

#define M 5                // 宏定义
#define PI 3.14            //宏定义
int a[M];                  // 会被替换为: int a[5];
int b = M;                 // 会被替换为: int b = 5;
printf("PI = %.2f\n", PI); // 输出结果为: PI = 3.14

注意 宏不是语句,结尾不需要加 “;”,否则会被替换进程序中,如:

#define N 10;               // 宏定义
int c[N];                   // 会被替换为: int c[10;]; 
//error:… main.c:133:11: Expected ']'

以上几个宏都是用来代表,所以被成为类对象宏(object-like macro,还有类函数宏,下面会介绍)

如果要写宏不止一行,则在结尾加反斜线符号使得多行能连接上,如:

#define HELLO "hello \
the world"

注意第二行要对齐,否则,如:

#define HELLO "hello the wo\
  rld"
printf("HELLO is %s\n", HELLO);
  //输出结果为: HELLO is hello the wo  rld

也就是,行与行之间的空格也会被作为替换文本的一部分。
而且由这个例子也可以看出:宏名如果出现在源程序中的 " " 内,则不会被当做宏来进行宏代换。

宏可以嵌套,但不参与运算:

#define M 5                 // 宏定义
#define MM M * M            // 宏的嵌套
printf("MM = %d\n", MM);    
// MM 被替换为: MM = M * M, 然后又变成 MM= 5 * 5

宏代换的过程在上句已经结束,实际的 5 * 5 相乘过程则在编译阶段完成,而不是在预处理器工作阶段完成,所以宏不进行运算,它只是按照指令进行文字的替换操作。再强调下,宏进行简单的文本替换,无论替换文本中是常数、表达式或者字符串等,预处理程序都不做任何检查,如果出现错误,只能是被宏代换之后的程序在编译阶段才发现。

宏定义必须写在函数之外,其作用域是 #define 开始,到源程序结束。如果要提前结束它的作用域则用 #undef 命令,如:

#define M 5                 // 宏定义
printf("M = %d\n", M);      // 输出结果为: M = 5
#define M 100               // 取消宏定义
printf("M = %d\n", M);      
// error:… main.c:138:24: Use of undeclared identifier 'M'

也可以用宏定义表示数据类型,可以使代码简便:

#define STU struct Student      // 宏定义STU
struct Student{                 // 定义结构体Student
    char *name; 
       int sNo;
};
STU stu = {"Jack", 20};         
// 被替换为:struct Student stu = {"Jack", 20};
printf("name: %s, sNo: %d\n", stu.name, stu.sNo);

这些简单的宏主要被用来定义那些显式常量(Manifest Constants),而且会使得程序更加容易修改,特别是某一常量的值在程序中多次被用到的时候,只需要改动一个宏定义,则程序中所有出现该变量的值都可以被改变。而且宏定义还有更多其他优点,如使得程序更容易理解,可以控制条件编译等。

#definetypedef 的区别:
两者都可以用来表示数据类型,如:

#define INT1 int
typedef int INT2;

两者是等效的,调用也一样:

INT1 a1 = 3;
INT2 a2 = 5;

但当如下使用时,问题就来了:

#define INT1 int *
typedef int * INT2;
INT1 a1, b1;
INT2 a2, b2;
b1 = &m;   //... main.c:185:8: Incompatible pointer to integer conversion assigning to 'int' from 'int *'; remove &
b2 = &n;   // OK

因为 INT1 a1, b1; 被宏代换后为: int * a1, b1; ,即定义的是一个指向int型变量的指针 a1 和一个int型的变量b1。而INT2 a2, b2; 表示定义的是两个变量a2和b2,这两个变量的类型都是INT2的,也就是int *的,所以两个都是指向int型变量的指针。
所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typedef 不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。

有参宏

C语言中宏是可以有参数的,这样的宏就成了外形与函数相似的类函数宏(function-like macro),如:

#define MEAN(x,y) (((x)+(Y))/2)    
//宏定义: 宏名(形参表),X,Y为宏的参数,(((x)+(Y))/2) 为替换主体
printf(“MEAN = %d\n”, MEAN(7, 9)); 
//宏调用: 宏名(实参表), 输出结果: MEAN = 8

和函数类似,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数
而且和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参。如:

#define M 5                          //无参宏
#define COUNT(M) M * M               //有参宏
printf("COUNT = %d\n", COUNT(10));   
// 替换为: COUNT(10) = 10 * 10     // 输出结果: COUNT = 100

这看上去用法与函数调用类似,但实际上是有很大差别的。如:

#define COUNT(M) M * M               //定义有参宏
int x = 6;
printf("COUNT = %d\n", COUNT(x + 1));// 输出结果: COUNT = 13
printf("COUNT = %d\n", COUNT(++x));  // 输出结果: COUNT = 56

这两个结果和调用函数的方法的结果差别很大,因为如果是像函数那样的话,COUNT(x + 1)应该相当于COUNT(7),结果应该是 7 * 7 = 49,但输出结果却是13。原因在于,预处理器不进行计算,只是进行字符串替换,而且也不会自动加上括号(),所以COUNT(x + 1)被替换为 COUNT(x + 1 * x + 1),代入 x = 6,即为 6 + 1 * 6 + 1 = 13。而解决办法则是:尽量用括号把整个替换文本及其中的每个参数括起来

#define COUNT(M) ((M) * (M))

但即使用括号,也不能解决上面例子的最后一个情况,COUNT(++x) 被替换为 ++x * ++x,即为 7 * 8 = 56,而不是想要 7 * 7 = 49,解决办法最简单的是:不要在有参宏用使用到“++”、“–” 等

上面说到宏名中不能有空格,宏名与形参表之间也不能有空格,而形参表中形参之间可以出现空格:

#define SUM (a,b) a + b              //定义有参宏
printf("SUM = %d\n", SUM(1,2));      
//调用有参宏。Build Failed!因为 SUM 被替换为:(a,b) a + b

如果用函数求一个整数的平方,则是:

int count(int x){
    return x * x;
    }

所以在宏定义中:#define COUNT(M) M * M 中的形参不分配内存单元,所以不作类型定义。而函数 int count(int x) 中形参是局部变量,会在栈区分配内存单元,所以要作类型定义,而且实参与形参之间是“值传递”。而宏只是符号代换,不存在值传递

宏定义也可以用来定义表达式或者多个语句。如:

#define JI(a,b) a = i + 3; b = j + 5;   //宏定义多个语句
int i = 5, j = 10;
int m = 0, n = 0;
JI(m, n);                       // 宏代换后为: m = i + 3, n = j + 5;
printf("m = %d, n = %d\n", m, n);      // 输出结果为: m = 8, n = 15

宏定义高级用法

# 运算符

比如我们宏定义了:

#define SUM (a,b) ((a) + (b))

我们想要输出“1 + 2 + 3 + 4 = 10”,用以下方式显得比较麻烦,有重复代码,而且中间还有括号:

printf("(%d + %d) + (%d + %d) = %d\n", 1, 2, 3, 4,SUM(1 + 2, 3+ 4));

那么这时可以考虑用 # 运算符来在字符串中包含宏参数,# 运算符的用处就是把语言符号转化为字符串。例如,如果 a 是一个宏的形参,则替换文本中的 #a 则被系统转化为 “a”。而这个转化的过程称为 “字符串化(stringizing)”。用这个方法实现上面的要求:

#define SUM(a,b) printf(#a " + "#b" = %d\n",((a) + (b)))    //宏定义,运用 # 运算符
SUM(1 + 2, 3 + 4);                                          //宏调用
//输出结果:1 + 2 + 3 + 4 = 10

调用宏时,用 1 + 2 代替 a,用 3 + 4 代替b,则替换文本为:printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),接着字符串连接功能将四个相邻的字符串转换为一个字符串:

"1 + 2 + 3 + 4 = %d\n"

## 运算符

和 # 运算符一样,## 运算符 也可以用在替换文本中,而它的作用是起到粘合的作用,即将两个语言符号组合成一个语言符号,所以又称为“预处理器的粘合剂(Preprocessor Glue)”。用法:

#define NAME(n) num ## n            //宏定义,使用 ## 运算符
int num0 = 10;
printf("num0 = %d\n", NAME(0));     //宏调用

NAME(0)被替换为 num ## 0,被粘合为: num0

可变宏:… 和 VA_ARGS

我们经常要输出结果时要多次使用 prinf(“…”, …),如果用上面例子 #define SUM(a,b) printf(#a ” + “#b” = %d\n”,((a) + (b))),则格式比较固定,不能用于输出其他格式。
这时我们可以考虑用可变宏(Variadic Macros)。用法是:

#define PR(...) printf(__VA_ARGS__)     //宏定义
PR("hello\n");               //宏调用   
//输出结果:hello

还有个例子如:

#define PR2(X, ...) printf("Message"#X":"__VA_ARGS__)   //宏定义
double msg = 10;
PR2(1, "msg = %.2f\n", msg);                            //宏调用
//输出结果:Message1:msg = 10.00

在宏调用中,X的值为10,所以 #X 被替换为”1”。宏代换后为:

printf("Message""1"":""msg = %.2f\n", msg);

接着这4个字符串连接成一个:

printf("Message1:msg = %.2f\n", msg);

要注意的是:省略号“只能用来替换宏的形参列表中最后一个

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值