10:预处理

预处理

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

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

1、宏替换

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

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

综上:P1 == 3.1415926

【注意】

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

带参数的宏定义

c 复制代码
#define MIN(a,b) (((a)<(b))?(a):(b))//参数a,b

①和函数不同,宏的参数没有数据类型,因为它是文本展开

②因为是文本展开,所以相比函数没有执行调度的开销,效率要高

③但是也同样是因为文本展开,所以在展开时可能会出现意想不到的情况

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

实验代码如下:

c 复制代码
#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
修改的代码如下:

c 复制代码
#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)

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

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

c 复制代码
#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

c 复制代码
#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 ...#endif
       1.2 双向分支
           #if ...#else ...#endif
       1.3 多向分支
           #if ...#else ...#else ...#else ...#endif
#endif:就是结束指令
        
2、#ifdef(如果宏定义了)
       2.1 单向分支
           #ifdef ...#endif
       2.2 双向分支
           #ifdef ...#else ...#endif
             
3、#ifndef(如果没有宏定义)
	   3.1 单向分支
           #ifndef ...#define ...#endif
       3.2 双向分支
           #ifdnef ...#else ...#endif

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

c 复制代码
#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)

实验代码如下

c 复制代码
#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

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

c 复制代码
#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个子串拼接到一起。
实验代码如下

c 复制代码
#include <stdio.h>

#define PTF(a) #a

int main(void)
{
	printf(PTF(Hello World\n));//printf("Hello World\n");
	return 0;
}

Hello World

c 复制代码
#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 = 123
	VAR_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后面加上##
相关推荐
wenchm30 分钟前
细说STM32单片机FreeRTOS任务管理API函数vTaskList()的使用方法
c语言·c++·stm32·单片机·嵌入式硬件
xueyinan4 小时前
小刚说C语言刷题——1039 求三个数的最大数
c语言
小柒的博客5 小时前
从C语言变量看内存
c语言·开发语言
YuforiaCode6 小时前
第十四届蓝桥杯 2023 C/C++组 飞机降落
c语言·c++·蓝桥杯
YuforiaCode7 小时前
第十四届蓝桥杯 2023 C/C++组 平方差
c语言·c++·蓝桥杯
JANYI20187 小时前
C语言中的双链表和单链表详细解释与实现
c语言·开发语言·windows
努力努力再努力wz7 小时前
【C++深入系列】:模版详解(上)
java·c语言·开发语言·c++
刚入坑的新人编程9 小时前
数据结构——栈和队列
c语言·数据结构·c++·链表·数组
YKPG9 小时前
c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第六式】文件操作
java·c语言·数据库
傍晚冰川9 小时前
【单片机 &C语言】单片机学习过程中常见C库函数(学习笔记)
c语言·笔记·stm32·单片机·学习·阿里云