自学内容网 自学内容网

预处理详解

咱们代码执行的过程大概是:预处理->编译->汇编->目标文件,接着链接器链接一下,形成可执行文件。这篇文章咱们来讲一下预处理。

1.预定义符号

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

他是在预处理阶段完成的,不过vs只能看到运行完的结果,如果想看预处理的,就需要你用vs code编译器,这边我没有下载,就不演示了

2.#define定义常量

如果定义的 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__ ) 
int main() {
DEBUG_PRINT;
}

 那我们思考一个问题,定义常量的后边要不要加一个分号?

答:不要。

定义常量只是做一个简单的替换

#include<stdio.h>
#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__ ) 
int main() {
//DEBUG_PRINT;
int n = MAX;
printf("%d", n);
//若不加分号,代码的含义:
//int n = 1000;
//printf("%d", n);
//若加分号,代码的含义:
//int n = 1000;;
//printf("%d", n);
return 0;
}

代码还能正常运行是因为,编译器把int n=1000;当成一句话,下句话是“;”,是一句空语句,所以能够正确运行,那假如是这种代码

所以我们一般不要加分号 

3.#define定义宏

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

宏的声明

#define name( parament-list ) stuff

注意:
参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分。//切记切记,否则酿成大错

这就是宏的一个使用方法,但是这个宏写的有漏洞,比如

why???

其实很简单 ,宏和定义常量一样,都是做简单的替换,例如在这个宏中,你传入的是5+1,他真的就给你把x替换成5+1了,然后5+1*5+1,就是11,那我们如何解决这个问题呢,就是加括号

这样就没有问题了,接下来来一道例题

#include<stdio.h>
#define DOUBLE(x) (x) + (x)
int main()
{
int n;
scanf("%d", &n);

printf("%d", 10 * DOUBLE(n));
}

大家可以猜一下当n=5时会输出什么?

答案很明显,因为宏只是简单的替换10*(5)+(5);自然还是55

 那要怎么改呢?整体加个括号就好了

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

 

注意,有些宏还带有副作用

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

例如:

n + 1;//无副作用
n++;//有副作用
#include<stdio.h>
#define MAX(x,y) (x)>(y)?(x):(y)
int main() {
int x, y;
scanf("%d%d", &x, &y);
int a = MAX(x++, y++);
printf("%d %d %d", x,y,a);
return 0;
}

大家可以想想输入5,8的时候会输出什么?

非常奇怪昂,但我们可以分析一下,刚开始输入5和8 ,首先他会把整个语句替换为(x++)>(y++)?(x++):(y++)。首先比较大小的时候全都加,x,y变成6和9.接着y大,返回y++接着在加一下,y变成10,返回y++是9.所以a是9,接着y是10,而x没有y大,不会执行后面的返回语句,所以就是个6.

宏与函数对比:

 4.#与##

我们先来说#

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

例如这段代码,#x的意思就是“x”。就是这样,所以当我们变量名改变的时候,他也能正确打印。

接着我们来说##。

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

例如:

#include<stdio.h>
#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;
}

这个说实话作用就是把int 和_MAX链接起来,形成int_MAX,这样的代码蛮有创造力,不过在实际中使用较少了,说实话。

4.宏定义的移除

移除了之后宏就相当于没有了,此时我们可以重新定义

5.条件编译

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

如当我们判断DEBUG是否被赋值时,就可以打印出来,当我们把上面那句话注释掉时,就不会打印出来了

#ifdef的意思就是if define,“如果定义了的话”就是这个意思。

常见的条件编译有

#if //常量表达式
//...
#endif
//常量表达式由预处理器求值。
//如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
//2.多个分⽀的条件编译
#if //常量表达式
//...
#elif //常量表达式
//...
#else
//...
#endif
//3.判断是否被定义
#if defined(symbol)
#endif
#ifdef symbol
#endif
#if !defined(symbol)
#endif
#ifndef symbol
#endif
4.嵌套指令

6.头文件包含

#include "filename"

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

#include <filename.h> 

查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件了。 对于较大的项目,往往有几千个源文件,这样查找效率就更加低了

还有一个文件是嵌套文件包含:
 

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
 
 return 0;
}

当一个文件被多次包含时,每次预处理都要在他身上花费额外的时间,如果是较大的工程,后果将不堪设想。

解决方法

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

或者

#pragma once

比如我们在vs中创建一个头文件,就可以看到

这句话的作用就是帮助我们每个文件只包含一次的 。

好的,这篇文章就到这里了,如果有帮助还请点个赞哦


原文地址:https://blog.csdn.net/spiritualfood/article/details/142498290

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!