一、c语言中的宏定义
1、基本概念
- #define是预处理器处理的单元之一
- #define定义的宏可以出现在代码中的任何位置
- #define定义之后的代码都可使用该宏
2、本质
#define定义的宏常量本质上是一个字面量,下面我们用一个例子来了解他的基本概念和本质。
注意下面没有加标准输入输出的头文件。
#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\test\
test.c
int main()
{
int err = ERROR;
char* p1 = PATH1;
char* p2 = PATH2;
char* p3 = PATH3;
}
下面我们在linux下对该代码做预处理。
gcc -E test.c -o test.i
此时可以顺利通过预处理,test.i的文件如下
int main()
{
int err = -1;
char* p1 = "D:\test\test.c";
char* p2 = D:\test\test.c;//可以看到直接替换
char* p3 = D:\testtest.c;//可以看到上面的/被test.c替换了
}
此时我们再完整编译该代码
gcc test.c
显然这里会是出错的,为什么呢?因为这里的将一个不合法的表示赋值给了一个指针。所以也就是说使用宏定义有它的好处也有它的缺点。下面来具体的讨论。
二、如何正确的使用宏
1、优点
①可以类似于函数调用来使用
②#define 表达式在某些情况下比函数更强大,如我们可以直接计算出数组的长度
2、缺点
有可能会导致难以发现的错误。
3、具体例子如下
#include <stdio.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 a = 1;
int b = 2;
int c[4] = {0};
int s1 = _SUM_(a, b);
int s2 = _SUM_(a, b) * _SUM_(a, b);
int m = _MIN_(a++, b);
int d = _DIM_(c);
printf("s1 = %d\n", s1);
printf("s2 = %d\n", s2);
printf("m = %d\n", m);
printf("d = %d\n", d);
return 0;
}
同样地我们也可以对该代码做预处理,注意此时可以将stdio.h注释掉
gcc -E test.c -o test.i
可以看到主函数如下:
int main()
{
int a = 1;
int b = 2;
int c[4] = {0};
int s1 = (a) + (b);
int s2 = (a) + (b) * (a) + (b);
int m = ((a++) < (b) ? (a++) : (b));
int d = sizeof(c)/sizeof(*c);
printf("s1 = %d\n", s1);
printf("s2 = %d\n", s2);
printf("m = %d\n", m);
printf("d = %d\n", d);
return 0;
}
然后同样的我们可以进行完整的编译,看看效果是不是如我们所预期的那样。
s2应该为9,但是结果却为3,具体为什么会这样,大家自己分析就可以了,而对于m=2,是由于a++了两次,但是事实上我们只需要+一次就可以了,所以这是它的缺点所在,但是我们却非常的方便的计算出了数组的长度。
三、宏表达式和函数的对比
1、基本了解
(1)宏表达式被预处理器处理,编译器不知道它的存在,而预处理器是不进行语法的检错的。
(2)函数的调用需要涉及到堆栈问题,因此需要资源开销,而宏表达式不需要。
(3)宏表达式中不能出现递归调用,如下面的式子是错误的。
(4)作用域上的区别。
#define _SUM_(n) ((n>0)?(SUM(n-1)+n):0)
int s=_SUM_(10);
#include <stdio.h>
void def()
{
#define PI 3.1415926
#define AREA(r) r * r * PI
}
double area(double r)
{
return AREA(r);
}
int main()
{
double r = area(5);
printf("PI = %f\n", PI);
printf("d = 5; a = %f\n", r);
return 0;
}
从作用域的角度出发,该程序是不合法的,但是事实上是可以通过编译的,因为宏定义之后的代码都可以使用它。所以这点和函数于变量的调用也是有区别的。下面是一些内置的宏,可以直接调用。
2、综合应用
#include <stdio.h>
#include <malloc.h>
#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)
#define FREE(p) (free(p), p=NULL)
#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s)
#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END }
int main()
{
int x = 0;
int* p = MALLOC(int, 5);
LOG("Begin to run main code...");
FOREACH(x, 5)
BEGIN
p[x] = x;
END
FOREACH(x, 5)
BEGIN
printf("%d\n", p[x]);
END
FREE(p);
LOG("End");
return 0;
}
运行结果如下:
该例程的宏定义在使用上都比函数要优越,且有些功能是无法通过函数来去实现的,很具有参考性。总的来说,宏定义的效率高于函数调用,但是使用不当也会带来很大的副作用。