一、宏定义定义常量
预定义符号
FILE//进行编译的源文件
LINE//文件当前的行号
DATE//文件被编译的日期
TIME//文件被编译的时间
STDC//如果编译器遵循ANSI C,其值为1,否则未定义
我们来看一段代码:
#include <stdio.h>void test_define1(){printf("file name is %s\n", __FILE__);printf("line is %d\n", __LINE__);printf("date is %s\n", __DATE__);printf("time is %s\n", __TIME__);printf("stdc is %d\n", __STDC__);}int main(){test_define1();return 0;}
输出结果:
预处理过程对宏定义的处理
在前面一节中我们了解到编译过程中有一步是预处理,我们再来看看编译过程:
其中预处理就包括了对宏定义的处理,预处理工作是系统引用预处理程序对源程序中的预处理部分做处理,而预处理部分是指以“#”开头的、放在函数之外的、一般放在源文件的前面的预处理命令,如:包括命令 #include,宏命令 #define 等,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。
我们来看看一段代码:
void test_define2(){#define PI 3.14int R = 2;float S = 0.0;S = R*R*PI;printf("S = %f\n", S);}int main(){//test_define1();test_define2();return 0;}
我们先将test.c预编译一下:
gcc -o test1.c -E test.c
然后打开预编译产生的文件test1.c:
void test_define2(){int R = 2;float S = 0.0;S = R*R*3.14;printf("S = %f\n", S);}int main(){test_define2();return 0;}
可以看到预编译将#define PI 3.14这个定义直接替换到使用PI的地方,也就是说预处理会将使用宏定义变量的地方直接进行替换处理。
宏定义原则:
#define定义宏常量可以出现在代码的任何地方
#define从本行开始,,之后的代码都可以使用这个宏常量
注意宏不是语句,结尾不需要加“;”,否则会被替换进程序中,如:
#define N 10;// 宏定义int c[N]; // 会被替换为: int c[10;]; //error:… main.c:133:11: Expected ']
如果重复定义宏,则不同的编译器采用不同的重定义策略。有的编译器认为这是错误的,有的则只是提示警告。Xcode中采用第二种方式。如:
#define M 5 //宏定义#define M 100//重定义,warning:… main.c:26:9: 'M' macro redefined
#define和typedef的区别
两者都可以用来表示数据类型,如:
#define INT1 inttypedef 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型变量的指针。
所以两者区别在于,宏定义只是简单的字符串代换,在预处理阶段完成。而typede不是简单的字符串代换,而是可以用来做类型说明符的重命名的,类型的别名可以具有类型定义说明的功能,在编译阶段完成的。
总之一句话:编译器对#define预处理就是直接进行文本替换
使用连接符扩展宏
当我们想要用#define进行像函数那样进行定义时,合理地利用预处理功能可以使得程序更加方便地阅读、修改、移植、调试等,也有利于模块化程序设计。这样未免会遇到#define语句过长的情况,比如:
#define HELLO "hello the world"
我们可以用连接符进行换行处理:
#define HELLO "hello \the world"
又比如我们用#define来定义一个函数:
#define ADD(a, b) \((a) + (b))void test_define3(){int a = 10;int b = 20;int sum = 0;sum = ADD(a, b);printf("sum is %d\n", sum);}int main(){test_define3();return 0;}
二、宏定义定义表达式
宏定义表达式的优势
#define表达式给有函数调用的假象,,却不是函数 却不是函数
#define表达式可以比函数更强大,移植性更好
#define表达式比函数更容易出错
我们先来看宏带参数的定义:
和函数类似,在宏定义中的参数成为形式参数,在宏调用中的参数成为实际参数。
而且和无参宏不同的一点是,有参宏在调用中,不仅要进行宏展开,而且还要用实参去替换形参。
宏定义表达式和函数的区别
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAX(x,y) ((x)>(y)?(x):(y))
对应这个定义的函数:
int Max(int x, int y){return x > y ? x : y;}
相对于函数来说:
(1).用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
(2).更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。
而且,宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以。
但是,宏也有劣势的地方,例如:
(1)每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
(2)宏是没法调试的。
(3)宏由于类型无关,也就不够严谨。
(4)宏可能会带来运算符优先级的问题,导致程容易出现错。
##的作用
这是一个连接符号,作用是把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
我们来看看一段代码:
#define STD(n) std##nint test_define4(){int STD(1);int STD(2);STD(1) = 1;STD(2) = 2;printf("%d\n", STD(1));printf("%d\n", STD(2));return 0;}int main(){test_define4();return 0;}
这里定义了student:#define STD(n) std##n,然后声明了两个student,值分别为1,2,我们来看看预编译产生的结果:
int test_define4(){int std1;int std2;std1 = 1;std2 = 2;printf("%d\n", std1);printf("%d\n", std2);return 0;}int main(){test_define4();return 0;}
可以看到预编译直接将std和参数n进行拼接处理
三、宏定义带来的副作用
指针处理的时候:
我们来看看一段代码:
还是用上面提到的:
#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型变量的指针。
自加,自减处理
来看一段代码:
#define COUNT(M) M * M//定义有参宏int x = 6;printf("COUNT = %d\n", COUNT(x + 1));// 输出结果: COUNT = 13printf("COUNT = %d\n", COUNT(++x)); // 输出结果: COUNT = 56
我们来看看预编译产生的:
void test_define5(){int x = 6;printf("COUNT = %d\n", x + 1 * x + 1);printf("COUNT = %d\n", ++x * ++x);}
看到预编译的结果我们可以明显看到为什么结果是13和56了,这个并不是我们想要的结果。
四、总结:
宏定义在预处理的时候被处理掉#define宏定义只是在预处理的时候做文本替换#define宏定义要加上必要的小括号,以免带来副作用