预处理深入

简介: 预处理深入

1.预定义符号

c语言中有一些预定义符号,也是在预处理阶段处理的,可以直接使用:

__FILE__       // 进行编译的源文件

__LINE__       // 文件当前的行号

__DATE__       // 文件被编译的⽇期

__TIME__       // 文件被编译的时间

__STDC__       // 如果编译器遵循ANSI C,其值为1,否则未定义

例如:

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


2.2.#define 定义常量

#define可以在mian函数外定义常量,用自定义的名字来表示

基本形式:

# 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__)


3.#define定义宏

#define 机制包括了⼀个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏。

宏的基本形式:

# define name( parament-list ) stuff

其中parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中

这里需要注意的是括号的左边一定要与name 紧邻,否则就有可能会被编译器误认为是stuff的一部分。

并且宏只是纯文本的替换,并不会计算 parament-list 里面的表达式。

看下面一段代码:

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


预处理替换后:

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


而不是:

int a = 5;
printf("%d\n" ,36);


所以我们在使用宏时一定要多加括号,以免出现以上错误:

#define SQUARE( x )  ((x) * (x))


4.带有副作用的宏参数

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

看以下两个区别

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);


经过预处理后:

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


我们可以看到x和y有可能出现两次自增。结果是

x=6 y=10 z=9


5.宏替换的规则

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。


6.宏函数的对比

宏通常被应用于执行简单的运算。

比如在两个数中找出较大的⼀个时,写成下面的宏,更有优势⼀些。

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


那为什么不用函数来完成这个任务?

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算⼯作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜⼀筹。

2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型。宏的参数是类型⽆关

的。


和函数相比宏的劣势:

1. 每次使用宏的时候,⼀份宏定义的代码将插⼊到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

2. 宏是没法调试的。

3. 宏由于类型⽆关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type)\
 (type )malloc(num sizeof(type))
 ...
//使⽤
 MALLOC(10, int);//类型作为参数
//预处理器替换之后:
 (int *)malloc(10 sizeof(int));


宏和函数的⼀个对比:

34e8cb8f50b44592a4f0bc66c8fa68c8.png

目录
相关文章
|
存储 Python
Python网络编程基础(Socket编程) UDP 发送和接收数据
【4月更文挑战第10天】对于UDP客户端而言,发送数据是一个相对简单的过程。首先,你需要构建一个要发送的数据报,这通常是一个字节串(bytes)。然后,你可以调用socket对象的`sendto`方法,将数据报发送到指定的服务器地址和端口。
|
前端开发 JavaScript
uniapp移动端悬浮按钮(吸附边缘)
uniapp移动端悬浮按钮(吸附边缘)
630 0
|
6月前
|
存储 人工智能 搜索推荐
炸裂!!!Deepseek接入个人知识库,回答速度飞起来,确实可以封神了
高效管理知识、快速获取信息成为提升工作效率的关键。无论是做技术的同学还是普通的上班族,在日常积累了大量的知识数据和内容。项目文档、会议记录到技术手册、业务流程,这些信息如同宝藏一般,等待着被高效利用。然而,面对海量的数据,如何快速准确地获取到自己想要的内容,成为了提升工作效率的关键挑战。这时,一个高效的知识库就显得尤为重要。今天,就给大家详细介绍如何利用DeepSeek和Cherry-Studio,搭建属于自己的高效专属知识库,让你在信息的海洋中如鱼得水。
381 1
|
8月前
|
安全 Java
Object取值转java对象
通过本文的介绍,我们了解了几种将 `Object`类型转换为Java对象的方法,包括强制类型转换、使用 `instanceof`检查类型和泛型方法等。此外,还探讨了在集合、反射和序列化等常见场景中的应用。掌握这些方法和技巧,有助于编写更健壮和类型安全的Java代码。
455 17
|
9月前
|
PHP Apache
【ThinkPHP框架教程·Part-04】URL访问模式
本章节介绍 ThinkPHP6.0 的 URL 访问模式,解析其访问方法。ThinkPHP 框架通过 URL 实现多种操作,默认为单应用模式。
【ThinkPHP框架教程·Part-04】URL访问模式
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
178 0
|
分布式计算 DataWorks Oracle
MaxCompute产品使用合集之如何创建表
MaxCompute作为一款全面的大数据处理平台,广泛应用于各类大数据分析、数据挖掘、BI及机器学习场景。掌握其核心功能、熟练操作流程、遵循最佳实践,可以帮助用户高效、安全地管理和利用海量数据。以下是一个关于MaxCompute产品使用的合集,涵盖了其核心功能、应用场景、操作流程以及最佳实践等内容。
284 7
|
Android开发 开发者
Android、Flutter为不同的CPU架构包打包APK(v7a、v8a、x86)
Android、Flutter为不同的CPU架构包打包APK(v7a、v8a、x86)
977 1
|
传感器 数据采集 人工智能
合肥中科深谷嵌入式项目实战——人工智能与机械臂(四)
合肥中科深谷嵌入式项目实战——人工智能与机械臂(四)
|
前端开发 JavaScript 开发者
playwright中定位元素的方法
playwright中定位元素的方法
512 1