预处理总共分为三种:宏定义(宏替换)、文件包含、条件编译
参考书籍:《C与指针的预处理篇》
一、宏定义(宏替换):
通用规则:------ 宏定义的符号要大写,用以区分标识符(变量、函数名等);
------ 宏定义的替换文本若是为表达式,则要用( )将表达式括起来;
------ 宏定义的替换文本若是太长,可以分行并且以反斜杠’ \‘作为每行的结尾,作为连接标志;
------ 不能在宏定义的末尾加' ; ',因为如果加了,那么连' ; '也会一起替换进去,而我们有时候是替换到语句中间的,那么就会变成语句了,而且就算是替换一语句 我们实际上再用宏时,也会在末尾加上' ; ' ,那么替换后就会造成有两个' ; ' ,这样也是错的,所以不论怎样都是错,我们就规定统一不允许在宏的后加' ; '
------ 双引号内部的宏名无效,即使出现了宏,也不能替换
系统定义好了的宏:_FILE_ 此宏所在的被编译的源文件名
_LINE_ 此宏所在的当前文件行号
_DATE_ 此宏所在文件被编译的日期
_TIME_ 此宏所在文件被编译的时间
宏定义与宏取消:在代码中,#define XX yy 用于定义宏,#undef XX 用于取消宏。
在命令行编译文件时,-DXX=yy 用于定义宏,-UXX 用于取消宏。
如:$gcc -DSIZE=100 prog.c
$gcc -USIZE prog,c
分为以下几种情况:
(1) 一般的符号常量的宏定义,用于替换一些如字符、数字、字符串、符号、简单语句等。
(2)连接字符串、连接成标识符。
(3)宏函数的定义,尤其要注意宏参数
(1) 一般的符号常量的宏定义,用于替换一些如字符、数字、字符串、符号、简单语句、多条语句等;
如:#define CH 'c' 字符
#define SIZE 100 数字
#define NAME "Lily" 字符串
#define db double 符号
#define PRINTF printf("line :%d",_LINE_) 简单语句
#define DO_FERVER for( ; ; )
#define CASE break ; case
若是多条语句则多条语句要用{ }括起来,防止如if 与 外部的else 联用了:
#define PRINTIF(n) { if(n) while(n) { printf("%d\t",n); (n) = (n) - 1; } }
还可用带有宏参数的宏来替换:
#define PRINTSTR(str) printf("%s\n",str);
(2)连接
连接字符串:
有两种方法:一种是替换文本为带双引号的字符串常量,则直接替换,因为相邻的字符串会自动连接成一个字符串:
注意:只能是宏与字符串常量之间进行连接或字符串常量与字符串常量进行连接或宏与宏进行连接,但不能是字符串常量与字符串指针的连接:
如:#define NAME “Linda”
printf("this is " NAME);
printf("these are " NAME "'s apple, all are %d\n",number);
#define PRINT(FORMAT,VALUE) \
printf("The value is "FORMAT "\n",VALUE)
则:PRINT("%d",int_a ); 相当printf("The value is%d\n",int_a);
PRINT("%f",float_b ); 相当printf("The value is%f\n",float_b);
PRINT("%c",char_c); 相当printf("The value is%c\n",char_c);
//这里的FORMAT就是字符串替换且连接,而VALUE就是简单的替换而已。
一种是替换文本为不带双引号的符号序列,则需要用 #转成字符串后再替换,用法为 #替换文本 :
如:#define PRINT(FORMAT,VALUE) \
printf("The value of "#VALUE "is" FORMAT “\n",VALUE);
则:PRINT("%d", x+3);想当于printf("The value of x+3 is %d “\n",x+3);
连接成标识符:(标识符可以是变量名、函数名等)
用##来连接两边的符号拼成标识符:
#define a(n) a##n //n作为宏参数
int a1 = 3 , a2 = 24 ,a3 = 9;
for(int i = 1;i<4;i++)
printf("%d\t",aN(i));
则相当于:printf("%d\t",a1);
printf("%d\t",a2);
printf("%d\t",a3);
##还会阻止外层调用参数的扩展,因为##会将参数连接成标识符,标识符不能够参与宏的替换,因为标识符不能识别宏:
如:#define CAT(x,y) x##y
那么CAT(CAT1,2),3)将生成的是CAT(1,2)3,而不是123,
因为外层的CAT替换后,就会将里层的CAT(1,2)和3作为标识符连接起来,既然作为标识符了,那么就不能被编译器识别宏,因为只对符号才识别宏,而标识符 不行
但若是#define XCAT(x,y) CAT(x,y)
那么XCAT(XCAT(1,2),3)将生成123
(3)宏函数
宏函数不同于函数,没有参数类型限制,它仅仅只是替换,所以可以适用于任何类型,有点类似于C++的函数模板:
如:ADD( 3 ,15);
ADD(19.62 , 20.03);
ADD('a' + 'm');
宏函数使用宏参数时要注意宏参数的副作用,因为宏参数每被替换一次都会进行一次参数求值,尤其要注意能改变参数值的宏参数:
如:#define MAX( a , b) ( (a) > (b) ? (a) : (b) )
则此时若是传入的参数在参数求值是本身能改变参数的值,则这样替换后得到的表达式就有副作用:
如 m = 3; n = 5; z = MAX(m++,n++); ,则得到的是 z = ((m++) > (n++) ? (m++) : (n++)); 得到的结果是m = 4, n = 7 ,z = 6 ,因为这样替换后的表达式本身是有副作用的,m被求值一次,可n是被求值了两次的,所以我们要遵守一个表达式的原则,即当表达式有一个参数时,那么就不允许再出想这个参数的自增或自减或 是能改变这个参数本身值的函数。
宏函数 VS 函数:
优点:宏函数的使用限制:
从执行速度上看:宏函数的使用使用是要看情况的,有时候使用宏函数益处多多,有时候使用的话则是得不偿失。当宏函数的代码仅短短三五行时,用宏 函数比函数要好,因为在调用、返回函数时本身开销是比较大的,那如果额外开销还大于做功开销(执行代码的开销),那就不值得了。
从通用性来看:宏函数因为是替换,所以可以用于不同类型的参数,而函数是比较单一的,只能用于固定的参数类型和返回值。
缺点:
从代码长度上看:因为宏函数是替换,而函数是调用,若代码本身比较长,则宏函数的使用相对调用函数来说会使代码增长。
从结果上看,宏函数比价不容易预测,除了内外要加两层次的()外,还要注意参数不能有副作用。
二、文件包含
本地包含首先是在当前目录或者是相对路径目录上进行对头文件的查找,若是找不到再到函数库头文件的标准路径/user/include上进行查找。
函数库头文件包含:---------以标准库为出发点
#include<stdio.h
直接进入标准库头文件路径/user/include进行查找
------------------------ ” “ 与< > 的作用是差不多的,但由于路径查询的原因,本地头文件不能用< > ,而标准函数库头文件用" " 也是可以的,因为< > 的查找比" " 更窄。
三、条件编译
表达式方式:
#if 表达式1
#ifdef
#define _ADT_H_
#define USE "drinking"
extern int pro_hour;
extern int pro_minute;
extern int pro_second;
int usesec(int h, int m,int s);
#endif