学过 C 语言的朋友们肯定知道
- 宏可以定义常量
- 宏可以定义代码块
但是大家真的了解宏了吗?我给大家分析分析。
我们应该知道:
- #define 是预处理器处理的单元实体之一
- #define 定义的宏可以出现在程序的任意位置
- #define 定义之后的代码都可以使用这个宏
来谈谈最开始我们说的定义宏常量吧。
- #define 定义的宏常量可以直接使用
- #define 定义的宏常量本质为字面量
怎么理解?
所谓字面量,其不占内存单元,没有存储空间。这与 const 定义的常变量有着巨大的区别。
那宏到底做了什么?我们来看看下面的几个宏常量定义,看看他们正不正确。
//宏定义
#define ERROR -1
#define MPATH1 "./test/a.c"
#define MPATH2 ./test/a.c
#define MPATH3 ./test/\
a.c
int main()
{
int error = ERROR;
char* s1 = MPATH1;
char* s2 = MPATH2;
char* s3 = MPATH3;
return 0;
}
既然宏是被预处理器所处理,想要知道我们的宏定义的对不对,就要让预处理器告诉我们。
单步编译
这说明我们的宏定义是正确的。
大家可能会纳闷,这样的代码直接编译可执行文件时会出错,但是这里单步编译却没错,为什么?
其实很简单,看看单步编译后的文件就知道了。
在这里插入图片描述
预处理阶段预处理器只是简单的将宏做了文本替换,不做语法检查。那谁来做语法检查?编译器呗。
我们来看看宏定义的表达式
- #define 表达式的使用类似函数调用
- #define 表达式可以比函数更强大
- #define 表达式比函数更容易出错
看看下面的宏表达式正确吗?
#include<stdio.h>
#include<stdlib.h>
//宏表达式
#define __SUM_(a,b) (a)+(b)
#define __MIN_(a,b) ((a)<(b)?(a):(b))
#define __DIM_(a) sizeof(a)/sizeof(*a)
int main()
{
int arr[10] = {0};
printf("sum = %d\n",__SUM_(3,9));
printf("min = %d\n",__MIN_(3,9));
printf("dim = %d\n",__DIM_(arr));
return 0;
}
结果
我们通过宏求出了两个数的和,两个数中的最小值,还得到了一个数组的数组大小。是不是觉得宏挺强大的,尤其是求数组大小。
看到这大家应该都很兴奋了吧,你们还记得我刚才说过的话吗,宏可以比函数强大,但是也比函数更容易出错。
看看修改后的代码
#include<stdio.h>
#include<stdlib.h>
#define __SUM_(a,b) (a)+(b)
#define __MIN_(a,b) ((a)<(b)?(a):(b))
#define __DIM_(a) sizeof(a)/sizeof(*a)
int main()
{
int arr[10] = {0};
printf("sum = %d\n",__SUM_(3,9));
printf("min = %d\n",__MIN_(3,9));
printf("dim = %d\n",__DIM_(arr));
int tmp1 = 1;
int tmp2 = 3;
//本意想求 (1+2)的平方
int a = __SUM_(1,2) * __SUM_(1,2);
//本意想求 tmp1 和 tmp2 两者间的最小值,求完值后让tmp1++
int b = __MIN_(tmp1++,tmp2);
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}
运行结果
好像a和b的值不对啊,wtf。
别急,老规矩,看看中间文件
问题找到了,预处理器在预处理的时候进行了文本替换,但是很显然他不理解我想干什么,本来我想先求1+2的值再相乘的,这里变成了先算乘法了。求最小值的那里tmp++做了两次啊。
原来宏表达式可以这么坑。当然,合理的使用宏表达式可以很好的帮助我们,至于这些坑嘛,我们只能规范自己来避免。
注意!!!
宏表达式不能递归定义
// error
#define __SUM_(n) ((n>0)?(__SUM_(n-1)+n):0)
- 宏表达式中不能出现递归定义
- 宏表达式被预处理器处理,编译器不知道宏表达式的存在
- 宏表达式用“实参”完全替代形参,不进行任何运算
- 宏表达式没有任何的“调用”开销
前面说了这么多,我们来讨论一下 C 语言中常讨论的问题,作用域。
宏定义有作用域限制吗?
看看下面的代码合法吗?
#include<stdio.h>
#include<stdlib.h>
void func()
{
#define a 100
#define __SUM_(a,b) (a)+(b)
}
int main()
{
int i = __SUM_(a,20);
printf("%d\n",i);
return 0;
}
运行结果
这是不是说明,即使是在 void func() 函数中定义的宏,在 main 中也能使用。
我们把函数位置调换一下
#include<stdio.h>
#include<stdlib.h>
void func();
int main()
{
int i = __SUM_(a,20);
printf("%d\n",i);
return 0;
}
void func()
{
#define a 100
#define __SUM_(a,b) (a)+(b)
}
编译过不了了。为什么?
其实宏在定义之后,后面的代码就可以使用这个宏了。作用域的概念是针对变量和函数的,他不针对宏,为什么啊?因为宏是预处理器处理的,编译器根本不知道宏标识符的存在,所以编译器不能把作用域的概念运用于宏。
C 语言中一些强大的内置宏
//注意,是两个下划线,左边两个右边也两个
__FILE__ 被编译的文件名
__LINE__ 当前行号
__DATE__ 编译时的日期
__TIME__ 编译时的时间
__STDC__ 编译器是否遵循标准C规范
番外
#include<stdio.h>
#include<stdlib.h>
#define MALLOC(type,n) (type*)malloc(sizeof(type)*n)
#define FREE(p) (free(p),p=NULL)
#define FOREACH(i,m) for(i = 0;i<m;i++)
#define BEGIN {
#define END }
int main()
{
int count = 10;
int* arr = MALLOC(int,10);
if(arr){
int i;
FOREACH(i,count)
BEGIN
arr[i] = i+1;
END
FOREACH(i,count)
BEGIN
printf("arr[%d] = %d\n",i,arr[i]);
END
FREE(arr);
}
if(!arr){
printf("arr now is NULL\n");
}
return 0;
}