10:预处理

devtools/2025/2/2 23:52:44/

预处理

  • 1、宏替换
  • 2、头文件包含
  • 3、条件编译
  • 4、typedef和#define的区别
  • 5、#define中的注意点
    • 5.1、使用do....while(0)
    • 5.2、#和##的含义

C语言编译器在编译程序之前,会先使用预处理器(预处理器)处理代码,代码经过预处理之后再送入编译器进行编译。预处理器的主要任务包括宏替换、文件包含、条件编译

预处理过程中会执行预处理指令,预处理指令以#号开头

1、宏替换

宏替换的预处理指令:#define叫做宏定义,语法格式为如下

#define 名字 值
#define P1 3.1415926
#define MM int//给int取小名MM,和typede类似

综上:P1 == 3.1415926

【注意】

  1. 结尾没有分号;,这一点和#包括以及所有预处理指令一样,都不是C语句
  2. 值可以是数字、表达式、代码语句等,总之什么都能替换;
  3. 和#包括一样,在预处理阶段执行,文本替换

带参数的宏定义

#define MIN(a,b) (((a)<(b))?(a):(b))//参数a,b

①和函数不同,宏的参数没有数据类型,因为它是文本展开
②因为是文本展开,所以相比函数没有执行调度的开销,效率要高
③但是也同样是因为文本展开,所以在展开时可能会出现意想不到的情况

【注意】使用带参数的宏定义,一定要注意在特定的地方加上()

实验代码如下:

#include <stdio.h>int main(void)
{int a;printf("请输入a的值:");scanf("%d",&a);int b = (a * 10/5 + 2)* 10;int c = (a * 10/5 + 2)* 20;int d = (a * 10/5 + 2)* 30;printf("b = %d\n",b);	printf("c = %d\n",c);printf("d = %d\n",d);return 0;
}

请输入a的值:10
b = 220
c = 440
d = 660

综上:代码中多次出现了a * 10/5 + 2,则可以使用宏来代替a * 10/5 + 2
修改的代码如下:

#include <stdio.h>#define GetNum(t)   t * 10/5 + 2//定义宏int main(void)
{int a;printf("请输入a的值:");scanf("%d",&a);int b = GetNum(a) * 10;int c = GetNum(a) * 20;int d = GetNum(a) * 30; printf("b = %d\n",b);	printf("c = %d\n",c);printf("d = %d\n",d);return 0;
}

请输入a的值:10
b = 40
c = 60
d = 80

为什么得出的结果和上面的不一样喃?问题就出现在少添加了()

int b = GetNum(a) * 10;等价于 a * 10/5 + 2 * 10。则运算的顺序改变了,导致计算结果不一样
修改的宏定义如下:
#define GetNum(t)   ((t) * 10/5 + 2)

总结:使用带参数的宏定义时①整个值的表达式都要()。②参数出现的每个地方都要()

带参数宏定义与函数的区别

#include <stdio.h>/* 定义函数计算立方 */
int Cude(int n)
{return n * n * n;
}
int main(void)
{int  i  = 1;while(i <= 5){printf("%d\n",Cude(i++));}return 0;
}

1
8
27
64
125

#include <stdio.h>#define CUBE(n) ((n)*(n)*(n)) //计算输入参数的立方int main(void)
{int  i  = 1;while(i <= 5){printf("%d\n",CUBE(i++));//CUBE(i++)替换为((i++)*(i++)*(i++))}return 0;
}

6
120

宏定义就只有一个功能:那就是替换,替换,替换。并不会像函数传递参数一样,会对参数进行计算

宏定义的作用域
作用域就是在定义此宏的文件。

2、头文件包含

头文件包含使用#include "xxx.h",如下图在main.c"文件里面包含各个头文件

在这里插入图片描述

./表示当前文件夹
../表示上一层文件夹
../../表示上上层文件夹#include "./myheader01.h"     				 //包含myheader01.h头文件
或者 #include "myheader01.h"#include "includes/myheader02.h"			 //包含myheader02.h头文件#include "../myheader03.h"					 //包含myheader03.h头文件#include "../../inc/myheader04.h"			 //包含myheader04.h头文件#include "../../myheader05.h"				 //包含myheader05.h头文件

3、条件编译

1、#if(如果)1.1 单向分支#if ...#endif1.2 双向分支#if ...#else ...#endif1.3 多向分支#if ...#else ...#else ...#else ...#endif
#endif:就是结束指令2、#ifdef(如果宏定义了)2.1 单向分支#ifdef ...#endif2.2 双向分支#ifdef ...#else ...#endif3、#ifndef(如果没有宏定义)3.1 单向分支#ifndef ...#define ...#endif3.2 双向分支#ifdnef ...#else ...#endif

最常用的就是第三个,一般用于xxx.h头文件中。示例代码如下:

#ifndef __SPI_H           //如果没有定义__SPI_H这个宏,如果定义了就直接结束,不在定义
#define __SPI_H	          //那么就去定义__SPI_H这个宏,然后执行下面的代码
#include "stm32f10x.h"
#include "Delay.h"void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SendRecByte(uint8_t Byte);#endif

4、typedef和#define的区别

  • 相同点
    都是给一个对象取一个别名,增强程序的可读性,但它们在使用时有

  • 不同点
    ①应用场景不同:
           1)类型定义用来给一种数据类型定义别名
           2)#define用来给数字、表达式、代码语句定义别名
    ②执行时机不同:
           1)typedef在编译阶段执行
           2)#define在预编译阶段执行
    ③定义方法不同:
           1)#define定义的别名在前面,并且不需要加分号;
           2)typedef定义的别名在后面,并且需要加分号;

5、#define中的注意点

5.1、使用do…while(0)

实验代码如下

#include <stdio.h>int main(void)
{int a,b;printf("请输入:");scanf("%d %d",&a,&b);if(a < b){//将a,b中的数据互换int temp;temp = a;a = b;b = temp; }printf("最大值a = %d\n",a);return 0;
}

请输入:12 30
最大值a = 30

使用宏定义来代替进行数据互换的代码:

#include <stdio.h>/*#define Chang(x,y) {\int temp;\temp = x;\x = y;\y = temp;\}	
*/
#define Chang(x,y) do{\int temp;\temp = x;\x = y;\y = temp;\}while(0)		int main(void)
{int a,b;printf("请输入:");scanf("%d %d",&a,&b);if(a < b){Chang(a,b);//将a,b中的数据互换}printf("最大值a = %d\n",a);return 0;
}

请输入:12 30
最大值a = 30

综上:上面代码中\为连接符号,上面2种宏替换都可以。

5.2、#和##的含义

在宏定义中常常可见###。那么都是什么含义喃?
#:双引号“ ”,转换为字符串
##:连接符,将参数的2个子串拼接到一起。
实验代码如下

#include <stdio.h>#define PTF(a) #aint main(void)
{printf(PTF(Hello World\n));//printf("Hello World\n");return 0;
}

Hello World

#include <stdio.h>#define LINK(x,y,z) x##y##z
#define VAR_COMB(name,size) char name_arr[size] = {0}
int main(void)
{int a = LINK(1,2,3);printf("%d\n",a);//a = 123VAR_COMB(var,10);//char name_arr[10] = {0}。参数name和_构成了一个整体了,则会导致参数丢失,return 0;
}
综上:#define VAR_COMB(name,size) char name_arr[size] = {0}修改为如下:
#define VAR_COMB(name,size) char name##_arr[size] = {0}//将参数name后面加上##

http://www.ppmy.cn/devtools/155571.html

相关文章

嵌入式硬件篇---CPUGPUTPU

文章目录 第一部分&#xff1a;处理器CPU&#xff08;中央处理器&#xff09;1.通用性2.核心数3.缓存4.指令集5.功耗和发热 GPU&#xff08;图形处理器&#xff09;1.并行处理2.核心数量3.内存带宽4.专门的应用 TPU&#xff08;张量处理单元&#xff09;1.为深度学习定制2.低精…

Linux环境变量

查看所有环境变量 printenv env cat /proc/self/environ 这个命令会显示所有环境变量&#xff0c;但变量之间用 \0&#xff08;空字符&#xff09;分隔&#xff0c;适合程序读取而不是直接查看。 P A T H 特殊的环境变量&#xff0c; PATH特殊的环境变量&#xff0c; PATH特殊的…

STM32 AD多通道

接线图&#xff1a; 代码配置&#xff1a; 与单通道相比&#xff0c;将多路选择从初始化函数&#xff0c;调用到功能函数里&#xff0c;在功能函数里以此调用需要使用的通道 整体代码&#xff1a; //AD多通道 void AD_Init2(void) {//定义结构体变量GPIO_InitTypeDef GPIO_In…

亚博microros小车-原生ubuntu支持系列:14雷达跟踪与雷达守卫

背景知识 激光雷达的数据格式参见&#xff1a; 亚博microros小车-原生ubuntu支持系列&#xff1a;13 激光雷达避障-CSDN博客 本节体验雷达跟踪跟守卫 PID控制 从百度百科摘一段介绍 比例积分微分控制&#xff08;proportional-integral-derivative control&#xff09;&am…

网络安全实战指南:攻防技术与防御策略

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 随着数字化转型的加速&#xff0c;网络安全已成为各行业不可忽视的重要领域。从数据泄露到勒索软件攻击&#xff0c;网络…

SpringCloud篇 微服务架构

1. 工程架构介绍 1.1 两种工程架构模型的特征 1.1.1 单体架构 上面这张图展示了单体架构&#xff08;Monolithic Architecture&#xff09;的基本组成和工作原理。单体架构是一种传统的软件架构模式&#xff0c;其中所有的功能都被打包在一个单一的、紧密耦合的应用程序中。 …

受击反馈HitReact、死亡效果Death Dissolve、Floating伤害值Text(末尾附 客户端RPC )

受击反馈HitReact 设置角色受击标签 (GameplayTag基本了解待补充) 角色监听标签并设置移动速度 创建一个受击技能&#xff0c;并应用GE 实现设置角色的受击蒙太奇动画 实现角色受击时播放蒙太奇动画&#xff0c;为了保证通用性&#xff0c;将其设置为一个函数&#xff0c;并…

SepLLM:大型语言模型中高效稀疏注意力的一种实用AI方法

大型语言模型&#xff08;Large Language Models&#xff0c;简称LLMs&#xff09;在自然语言处理领域展现出了其无与伦比的才华&#xff0c;无论是文本生成还是语境推理&#xff0c;都游刃有余。然而&#xff0c;其自注意力机制的二次复杂性却如同一道枷锁&#xff0c;限制了其…