预编译详解

预定义符号

  1. 预定义符号
    C语⾔设置了⼀些预定义符号,可以直接使⽤,预定义符号也是在预处理期间处理的。
    FILE //进⾏编译的源文件
    LINE //文件当前的行号
    DATE //文件被编译的日期
    TIME //文件被编译的时间
    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机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏
(define macro)。
下⾯是宏的申明方式:

#define name( parament-list ) stuff

举例:

#define SQUARE( x ) x * x

虽然定义宏也具备着调用函数的功能,但也有缺点:

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

其中我们可以看出将x替换为了a+1,但是运行结果却不是36,而是11。
原因:

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

所以替换为了5+1*5+1,先算乘法,再算加法,得出11。
为防止出错,我们再宏定义时应该根据实际情况加上括号,防止运算逻辑出错。

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

带有副作用的宏参数

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

x+1;//不带副作⽤ 
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);//输出的结果是什么?

可以看出这样的写法不利于计算,并且答案是6,10,9,不是我们预期的结果。
这就是为什么不利于用x++在宏定义中出现。

宏替换的规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
  2. 替换文本随后被插入到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
    述处理过程。
    注意:
  4. 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。类似于调用函数的嵌套。
  5. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

宏函数的对比

在这里插入图片描述
总的来说:宏不擅长定义复杂的计算,且会增加代码的长度,但在实现简单的计算的情况下要强于函数,并且不计类型都可以使用。

#和##

#运算符

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

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

如果将#去掉,那么n就会被视为被定义的变量,在预处理阶段就会被替换掉。

##运算符

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

为记号粘合
这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
这⾥我们想想,写⼀个函数求2个数的较大值的时候,不同的数据类型就得写不同的函数。
⽐如:

int int_max(int x, int y)
{
 return x>y?x:y;
}
float float_max(float x, float y)
{
 return x>yx:y;
}

但是这样写起来太繁琐了,现在我们这样写代码试试:

//宏定义 
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
 return (x>y?x:y); \
}
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名 
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名 
int main()
{
 //调⽤函数 
 int m = int_max(2, 3);
 printf("%d\n", m);
 float fm = float_max(3.5f, 4.5f);
 printf("%f\n", fm);
 return 0;
}

结果:
在这里插入图片描述

命名约定

⼀般来讲函数的宏的使⽤语法很相似。所以语言本⾝没法帮我们区分⼆者。
那我们平时的⼀个习惯是:
把宏名全部大写
函数名不要全部大写

#undef

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

#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除

命令行定义

许多C的编译器提供了⼀种能⼒,允许在命令⾏中定义符号。⽤于启动编译过程。
例如:当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候,这个特性有点⽤处。(假定某
个程序中声明了⼀个某个⻓度的数组,如果机器内存有限,我们需要⼀个很⼩的数组,但是另外⼀个
机器内存⼤些,我们需要⼀个数组能够⼤些。)

条件编译

在编译⼀个程序的时候我们如果要将⼀条语句(⼀组语句)编译或者放弃是很⽅便的。因为我们有条
件编译指令。比如说:
调试性的代码,删除可惜,保留⼜碍事,所以我们可以选择性的编译。

#include <stdio.h>
#define __DEBUG__
main()
{
 int i = 0;
 int arr[10] = {0};
 for(i=0; i<10; i++)
 {
 arr[i] = i;
 #ifdef __DEBUG__
 printf("%d\n", arr[i]);//为了观察数组是否赋值成功。  
 #endif //__DEBUG__
 }
 return 0;
}

常见的指令:
在这里插入图片描述
在这里插入图片描述

头⽂件的包含

#include "filename"//本地文件的包含
#include <stdio.h>//库文件的包含

嵌套文件包含
我们已经知道, #include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的
地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。

如果直接多次包含相同头文件,列如test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。
如果test.h文件比较大,这样预处理后代码量会剧增。如果⼯程比较大,有公共使用的头⽂件,被大家
都能使⽤,又不做任何的处理,那么后果真的不堪设想。
如何解决头⽂件被重复引⼊的问题?答案:条件编译。
每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容 
#endif //__TEST_H__

或者:

#pragma once

其他预处理指令

#error
#pragma
#line
...
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值