预处理详解

一、 预定义符号

C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的

__FILE__    //进⾏编译的源⽂件,打印要用字符串  
__LINE__    //⽂件当前的⾏号 ,%d
__DATE__    //⽂件被编译的⽇期 %s
__TIME__    //⽂件被编译的时间 %s
__STDC__   //如果编译器遵循ANSI C,其值为1,否则未定义

举个例子:

 printf("file:%s line:%d\n", __FILE__, __LINE__);

二、 #define 定义常量

#define name  stuff
#define MAX 1000
 #define reg register   //为 register这个关键字,创建⼀个简短的名字       
#define do_forever for(;;)  //⽤更形象的符号来替换⼀种实现   
#define CASE break;case  //在写case语句的时候⾃动把break写上      
// 如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
 #define DEBUG_PRINT printf("file:%s\tline:%d\t \
 date:%s\ttime:%s\n" ,\
 __FILE__,__LINE__ ,  \     
__DATE__,__TIME__ )

在define定义标识符的时候,最好不要在最后加上;,因为在你使用时很可能会出现两个;符号,就会多出一个空语句

三、#define定义宏

#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)
其实和上面的定义常量差不多,但是有更深层的研究

#define name( parament-list ) stuff

其中的parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分

#define SQUARE( x )  x * x

这个宏接收⼀个参数x,如果在上述声明之后,你把SQUARE( 5 ); 置于程序中,预处理器就会⽤下⾯这个表达式替换上⾯的表达式:5*5
但是这样写也会有问题:

 int a = 5;
 printf("%d\n" ,SQUARE( a + 1) );

这个打印的可不是36,而是11,因为其会被替换成这样:

 printf ("%d\n",a + 1 * a + 1 );

解决这个问题只需要:

 #define SQUARE(x)  (x) * (x)
 printf ("%d\n",(a + 1) * (a + 1) );

但这样还是容易出现别的错误:

 int a = 5;
 printf("%d\n" ,10 * DOUBLE(a));
 printf ("%d\n",10 * (5) + (5));

最好的解决办法就是再加上一个大括号

 #define DOUBLE(x)   ( ( x ) + ( x ) 

四、带有副作⽤的宏参数

当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可能出现危险,导致不可预测的后果。副作⽤就是表达式求值的时候出现的永久性效果

 x+1;//不带副作⽤ 就是说x加一并不会影响x本身的值
x++;//带有副作⽤

再给个具体例子:

 #define MAX(a, b)  ( (a) > (b) ? (a) : (b) )
 ...
 x = 5;
 y = 8;
 z = MAX(x++, y++);
 printf("x=%d y=%d z=%d\n", x, y, z);/

宏定义并不像函数一样就只是把值传过去,而是把整个式子都传了过去:

 z = ( (x++) > (y++) ? (x++) : (y++));

宏替换的规则

在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换(就是检查宏定义中是否还包含了宏定义)
替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换
最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

五、宏函数的对⽐

宏通常被应⽤于执⾏简单的运算。
⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些

 #define MAX(a, b) ((a)>(b)?(a):(b))

在某些方面其比函数更高效,更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于>
来⽐较的类型。宏的参数是类型⽆关的
但宏也有自己的不足:

  1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。
  2. 宏是没法调试的。
  3. 宏由于类型⽆关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
    宏有时候可以做函数做不到的事情。⽐如:宏的参数可以出现类型,但是函数做不到
 #define MALLOC(num, type)\
 (type *)malloc(num  sizeof(type))
 ...
 //使⽤
 MALLOC(10, int);//类型作为参数
 //预处理器替换之后:
 (int *)malloc(10  sizeof(int));

在这里插入图片描述

六、 #和##

#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执⾏的操作可以理解为”字符串化“
当我们有⼀个变量int a = 10; 的时候,我们想打印出: the value of a is 10
就可以写:

#define PRINT(n) printf("the value of "#n " is %d", n);

代码就会被预处理为:

 printf("the value of ""a" " is %d", a);

把a单独从字符串中提出来是因为能把a进行赋值并打印,但如果放在字符串里面它打印的就是the value of n,不会把a打印出来

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符,## 被称为记号粘合

比如说,我们想要定义不同数据类型的比大小的函数

 //宏定义 
#define GENERIC_MAX(type)      \
 type type##_max(type x, type y)\
 {                          \    
return (x>y?x:y);      \   
}
GENERIC_MAX(int) 
GENERIC_MAX(float)

但在实际中用的很少

#undef

这条指令⽤于移除⼀个宏定义。

#undef NAME//之前已经被宏定义过了
 //如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。

七、 条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。
因为我们有条件编译指令
在这里插入图片描述
在这里插入图片描述

八、头⽂件的包含

 #include "filename"//这是本地的头文件

查找策略:
先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头⽂件
如果找不到就提⽰编译错误

 #include <filename.h>//库文件

查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误

嵌套⽂件包含

在这里插入图片描述
如果直接这样写,test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。
如果test.h⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家都能使⽤,⼜不做任何的处理,那么后果真的不堪设想
其实只要用条件编译就可以解决这个问题

//头⽂件的内容
#ifndef __TEST_H__
 #define __TEST_H__//定义过一次之后,下次再调用这个头文件时,就不会执行了
#endif   //__TEST_H_
 #pragma once//写在头文件的开头,是比较符合现在人们习惯的一种写法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值