当前位置: 首页 > news >正文

预处理详解(完结篇)

⽬录
一. 什么是预处理
    1  预定义符号
    2. #define定义常量
    3 . #define定义宏
    4. 带有副作⽤的宏参数
    5. 宏替换的规则
    6 宏函数的对⽐
#和##
    1  #运算符
    2  ##运算符
    3  命名约定
四  #undef
五  条件编译

一      什么是预处理

有许多文件中都内容我们是看不懂的,那怎么才能令我们看得懂呢?其实我们的系统拿到各种文件后,要进行一系列的操作过程,才能将文件转化成我们能够看得懂的信息。而翻译环境能够把源代码转换成可执行的机器指令,这个过程主要有编译和链接两个过程组成。

编译链接的过程:

预处理就是编译和链接过程中的一个阶段。在预处理阶段,源文件和头文件都会被处理成.i为后缀的文件,接下来我将详解预处理相关知识。

  预处理指令

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

预定义符号:

1     __FILE__ //进⾏编译的源⽂件
2     __LINE__ //⽂件当前的⾏号
3     __DATE__ //⽂件被编译的⽇期
4     __TIME__ //⽂件被编译的时间
5     __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
注意:需要注意的是,__STDC__无法在vs中使用。

举个例子:

输出结果:

#define 定义常量 
2.1 基本语法:这里 需要特别注意的是,define定义常量时不要再define行末尾加上“;”,如果 加上 “;” 这样容易导致问题。
# define name stuff
|         |
名字      内容

2.2  举个例⼦: 

3     #define定义宏

3.1  概念:#define机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(define macro)。

那我们来段代码感受一下宏/定义宏

  我们定义了一个宏ADD(a,b),又因为允许把参数替换到⽂本中,所以这个宏ADD(a,b)可以计算传入宏的两个参数a,b的和

3.2   宏的申明⽅式:

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

3.3  使用宏/定义宏可能遇见的错误

示例:

输出结果:

从输出结果上来看,这个结果很令人出乎意料我们想得到6*7的结果,那为什么结果是13呢?这就要了解一下宏/定义宏的运行原理:其实,我们在把两个参数传过去后,(其原理是将参数在不计算的情况下直接传参传参过去)计算的其实是5+1*3+1,根据运算法则,,先算乘法再算加法,所以输出结果为13。

那怎么解决呢?我们想如果先计算a+1,在计算b+1,再计算a+1的结果*b+1的结果,这时候我们用括号把a+1括起来先计算不就可以吗?

这时候我们就得到我们想要的结果了。

总结:所以⽤于对数值表达式进⾏求值的宏定义都应该⽤这种⽅式加上括号,避免在使⽤宏时由于参数中的操作符或邻近操作符之间不可预料的相互作⽤。
   带有副作⽤的宏参数

4.2什么是副作⽤

假如我们想令一个数+1,但是这个数自己的大小不能变:

1   x+ 1 ; //不带副作⽤  x未变
2    x++;//带有副作⽤  x变大了

4.2带来的后果:宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可能出现危险,导致不可预测的后果

示例:

输出结果:

我们来分析一下为什么会出现这个结果,首先我们(利用宏原理)先把宏的值全都替换掉得到:

printf("%d %d %d",a,b, ((a++) > (b++) ? (a++) : (b++)));

再分析其过程: a++的特性是先用后加,所以我们实际比较的是4和5两个数。再比较完之后,a变成5,b变成6。然后返回值为b++。同样是先用后加,所以先返回b的值6,然后再自增1,现在b等于7。

5    宏替换的规则
在程序中扩展#define定义符号和宏时,需要涉及⼏个步骤。
1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。
2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
       2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
6    宏函数的对⽐

我们看到这应该已经能发现了,宏和函数功能及其相似,那他们两个到底谁更好呢

和函数相⽐宏的优势:
1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆关 的。
和函数相⽐宏的劣势:
1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。
2. 宏是没法调试的。
3. 宏由于类型⽆关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
所以如果是简单的问题例如: 在两个数中找出较⼤的⼀个时, 写成宏,更有优势⼀些 如果是复杂的问题,使用函数更有优势
两者多个方面对比:

三    #和##

1  #运算符

1.1作用:#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中 #运算符所执⾏的操作可以理解为”字符串化“

1.2形式:

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

1.3理解#运算符

示例:比如当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .

或者当我们有⼀个变量float  b=5.0;的时候,我们想打印出: the value of a is 5.0 .

运行结果:

从结果上其中n变量并未替换这是为什么呢?因为宏将n变量识别为一个字符,所以我们这里要使用#运算符将宏的⼀个参数转换为字符串字⾯量。那我们运用#运算符试一下

运用#运算符

运算结果:

从结果分析我们运用 #运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中,从而防止变量识别为字符。

2  ##运算符

2.1作用:## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称为记号粘合。这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的

示例:

 这里我们就是把type和MAX合并成了一个函数名,如果未使用##运算符其中的type_MAX不会理解为int_MAX而是理解为type_MAX。实际上我们很少用到#操作符和##操作符,但是当我们看到#操作符和##操作符时要知道这是什么意思

3   命名约定

因为⼀般来讲函数的宏的使⽤语法很相似。为了方便我们自己后续方便分清函数和宏

那我们平时的⼀个习惯是:(这个根据自己感觉有用就用,自己设定也行
把宏名全部⼤写
函数名不要全部⼤写

四   #undef

1作用:我们可以用#undef来移除一个宏定义

2示例

未使用#undef

使用#undef:

五  条件编译 

1作用:在编译⼀个程序的时候我们如果使用条件编译可以决定⼀条语句(⼀组语句)编译或者放弃。
2分类:
1   # if 常量表达式
    //...
    # endif
//常量表达式由预处理器求值。
2 多个分⽀的条件编译
# if 常量表达式
//...
# elif 常量表达式
//...
# else
//...
# endif

3判断是否被定义

第一种判断已定义:

# if defined(symbol)   或# ifdef symbol
第二种 判断未定义:
# if !defined(symbol)   或# ifndef symbol
 
4. 嵌套指令
# if defined(OS_UNIX)
# ifdef OPTION1
3 示例

因为#define MAX已定义所以会打印hehe1。

  #if和if的区别是,如果不满足条件的话,#if后的语句根本就不进行编译,这样会节省系统执行的时间。

本篇文章就到此结束,欢迎大家订阅我的专栏,欢迎大家指正,希望有所能帮到读者更好了解预处理相关知识 ,觉得有帮助的还请三联支持一下~后续会不断更新C/C++相关知识,我们下期再见。


http://www.mrgr.cn/news/78769.html

相关文章:

  • 4399大数据面试题及参考答案(数据分析和数据开发)
  • IDEA自定义帆软函数步骤详解
  • 解决“ VMware Tools for Windows Vista and later“报错问题
  • Hive元数据表解析
  • 联合汽车电子嵌入式面试题及参考答案
  • SAP SD学习笔记17 - 投诉处理3 - Credit/Debit Memo依赖,Credit/Debit Memo
  • 第144场双周赛:移除石头游戏、两个字符串得切换距离、零数组变换 Ⅲ、最多可收集的水果数目
  • Formality:设置Automated Setup Mode模式
  • 《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》
  • redis面试复习
  • Spring Shell如何与SpringBoot集成并快速创建命令行界面 (CLI) 应用程序
  • QT5 Creator (Mingw编译器) 调用VS2019 (阿里云 oss C++库) 报错的解决方法
  • Python毕业设计选题:基于django+vue的智慧社区可视化平台的设计与实现+spider
  • 快速学习GO语言总结
  • livekit 服务部署
  • 计算机的错误计算(一百七十一)
  • SQL进阶——聚合函数与分组
  • 给定一个整数可能为正,0,负数,统计这个数据的位数.
  • 【NebulaGraph】深入了解查询语句(二)
  • 数据结构 (21)树、森林和二叉树的关系