一节课教你学会【预处理详解】

news2024/11/26 14:34:30

谢谢观看!希望以下内容帮助到了你,对你起到作用的话,可以一键三连加关注!你们的支持是我更新地动力。
因作者水平有限,有错误还请指出,多多包涵,谢谢!


预处理详解

  • 一、预定义符号
  • 二、#define定义常量
  • 三、#define定义宏
  • 四、带有副作用的宏参数
  • 五、宏替换的规则
  • 六、宏和函数的对比
  • 七、`#`和`##`
    • 7.1`#`运算符
    • 7.2 `##`运算符
  • 八、命名约定
  • 九、#undef
  • 十、命令行定义
  • 十一、条件编译
  • 十二、头文件的包含
    • 12.1 头文件被包含的方式:
      • 12.1.1本地文件包含
      • 12.1.2库文件包含
    • 12.2 嵌套文件包含
  • 十三、其他预处理指令

一、预定义符号

  C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __DATE__);
	printf("%d\n", __LINE__);
	return 0;
}

在这里插入图片描述


二、#define定义常量

//基本语法
#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__ )

思考:在define定义标识符的时候,要不要在最后加上 ; ?

#define MAX 1000;
#define MAX 1000

建议不要加上 ; ,这样容易导致问题。

比如下面的场景:

if(condition)
 max = MAX;
else
 max = 0;

如果是加了分号的情况,等替换后,ifelse之间就是2条语句,
⽽没有⼤括号的时候,if后边只能有⼀条语句。这⾥会出现语法错误。

三、#define定义宏

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

下面是宏的申明方式:

#define name( parament-list ) stuff
parament-list表示参数列表
stuff表示内容
name表示宏的名字 
其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。

注意:

参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

举例:

#define SQUARE(n) n*n
int main()
{
int x = 0;
scanf("%d", &x);
int ret = SQUARE(x);  //实际替换为 int ret = x*x;
printf("%d", ret);
return 0;
}

警告:
上面这个宏存在一个问题:

#define SQUARE(n) n*n
int main()
{
int ret = SQUARE(5+1);  
printf("%d", ret);//结果是多少?咋一看是36,其实结果是11
return 0;
}

那为什么是11而不是36呢?

int ret = SQUARE(5+1);//其中SQUARE(5+1)实际替换为 5+1*5+1=5+5+1=11
想解决这个问题,只需要将#define SQUARE(n) n*n 改为 #define SQUARE(n) ((n)*(n))

总结:宏的列表是整个替换文本,所以有时需要加括号来保证代码的健壮性。

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

四、带有副作用的宏参数

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

x+1;//不带副作⽤
x++;//带有副作⽤
//为了让b得到11
int a = 10;
int b = a+1;//b=11,a=10   无副作用
int b = ++a;//b=11,a=11   有副作用,a的值永久的改变了

MAX宏可以证明具有副作用的参数所引起的问题。

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 10, b = 20;
	int m = MAX(a++, b++);
	//实际替换为int m = ((a++)>(b++)?(a++):(b++));
	//a=11 , b=22 , m=21
	printf("%d", m);//结果为21
	return 0;
}

五、宏替换的规则

在这里插入图片描述


六、宏和函数的对比

宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个时,写成下面的宏,更有优势一些。

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

那为什么不用函数来完成这个任务?
因为执行函数需要多步骤

  1. 调用函数的准备工作
  2. 执行函数的核心运算
  3. 从函数调用中返回值
原因有二:
1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。
所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。
反之,这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆关的。

和函数相比宏的劣势:

1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序
的⻓度。
2. 宏是没法调试的。因为调试时代码已经运行起来了,而宏在编译阶段中的预处理(预编译)段已经进行了文本替换,宏就没有了,所以根本无法调试
3. 宏由于参数类型⽆关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

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

#define Malloc(n,type) (type*)malloc(n*sizeof(type))
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	int* p2 = Malloc(10, int);//简化代码量,宏可以传类型,但函数不可以传类型
	free(p1, p2);
	return 0;
}

宏和函数的对比:如图所示点击这里


七、###

7.1#运算符

#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执⾏的操作可以理解为”字符串化“。
当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .

在这里插入图片描述
补充知识:

printf("I am lisi\n");
printf("I " "am " "lisi" "\n");
//打印出来的结果一样,""可以连接起来
上面用定义宏来简化操作,确实达到了想要的效果,但是执行代码出的结果中,发现变量n无法与传入的参数变量一一对应起来
可以通过操作符#来达到想要的目的

只需要将
#define PRINT(format,n)  printf("The value of     n is "format"\n",n)
改为
#define PRINT(format,n)  printf("The value of " #n "is "format"\n",n)
就行

在这里插入图片描述

总结:当将a、b、f变量代入替换时,#n会变为"a""b""f".

7.2 ##运算符

## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称
为记号粘合
这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
规则:标识符由字母、数字、下划线组成,并且首字母不能是数字
//宏定义,生成函数的模版
#define GENERIC_MAX(type) \
type type##_max(type x, type y) \
{ \
 return (x>y?x:y); \
}

//使用上面的模版来定义函数

GENERIC_MAX(int)  //实际替换为:int int_max(int x, int y) { return (x > y ? x : y); }
//替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名

GENERIC_MAX(float) //实际替换为:flaot float_max(float x, float y) { return (x > y ? x : y); }
//替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名

int main()
{
	//调⽤函数
	printf("%d\n", int_max(3, 5));
	
	printf("%f\n", float_max(3.0, 5.0));
	return 0;
}

在实际开发过程中##使用的很少,很难取出非常贴切的例子


八、命名约定

⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。
那我们平时的⼀个习惯是:
把宏名全部⼤写
函数名不要全部⼤写

九、#undef

#undef NAME
这条指令⽤于移除⼀个宏定义。
#define M 100
int main()
{
	printf("%d\n", M);
#undef M
	printf("%d\n", M);//会报错
	return 0;
}

在这里插入图片描述


十、命令行定义

许多C 的编译器提供了⼀种能⼒,允许在命令⾏中定义符号。⽤于启动编译过程。
例如:当我们根据同⼀个源⽂件要编译出⼀个程序的不同版本的时候,这个特性有点⽤处。(假定某
个程序中声明了⼀个某个⻓度的数组,如果机器内存有限,我们需要⼀个很⼩的数组,但是另外⼀个
机器内存⼤些,我们需要⼀个数组能够⼤些。)

在这里插入图片描述

编译指令:
//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c

十一、条件编译

1.
#if 常量表达式
 //...
#endif
--------------------------------------
int main()
{
#if 1   //1为真,进行编译
	printf("hehe\n");
#endif
	return 0;
}


int main()
{
#if 0   //0为假,不进行编译
	printf("hehe\n");//在预处理阶段就被删除了,也就是不进行编译
#endif
	return 0;
}

int main()
{
int a = 2;
#if a==2    //不进行编译,需要为常量表达式,而且局部变量是在执行环境的才创建的,而预处理阶段早在编译阶段就进行了,那个时候还没有a
	printf("hehe\n");
#endif
	return 0;
}
2.多个分⽀的条件编译//与第一个类似
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
3.判断是否被定义
#if defined(symbol)//写法1
        //...      //只要symbol被定义了,就会执行
#endif

#ifdef symbol//写法2
     //...       //只要symbol被定义了,就会执行
#endif


#if !defined(symbol)//写法1
    //...       //symbol没有被定义,就会执行
#endif

#ifndef symbol//写法2
    //...       //symbol没有被定义,就会执行
#endif
4.嵌套指令
#if defined(OS_UNIX)
		#ifdef OPTION1
 			unix_version_option1();
 		#endif
 		#ifdef OPTION2
 			unix_version_option2();
 		#endif
#elif defined(OS_MSDOS)
		#ifdef OPTION2
 			msdos_version_option2();
 		#endif
#endif

十二、头文件的包含

12.1 头文件被包含的方式:

  头文件的包含有2种形式:

第一种:
#include<stdio.h> //库文件包含,一般指标准库中头文件的包含
第二种:
#include"xxx.h"   //本地文件包含,一般指自己创建的头文件的包含

12.1.1本地文件包含

#include"xxx.h"   //本地文件包含,一般指自己创建的头文件的包含
查找策略:先在源⽂件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在
标准位置查找头⽂件。
如果找不到就提⽰编译错误。

然而查找标准位置在不同环境下是不一样的
在这里插入图片描述

12.1.2库文件包含

#include<stdio.h> //库文件包含,一般指标准库中头文件的包含
// 尖括号符号<filename>表示去标准库中查找头文件 
查找策略:
查找头⽂件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤ “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库⽂件还是本地⽂件
了。

12.2 嵌套文件包含

我们已经知道, #include 指令可以使另外⼀个⽂件被编译。就像它实际出现于 #include 指令的
地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并⽤包含⽂件的内容替换。
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。
//test.c
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
 
 return 0;
}
//test.h
void test();
struct Stu
{
 int id;
 char name[20];
};

通过上面test.ctest.h二个文件,在编译阶段中预处理阶段会对#include进行处理,处理很简单:预处理器先删除这条指令,并用包含文件的内容替换。变换后如下代码:

void test();
struct Stu
{
 int id;
 char name[20];
};
void test();
struct Stu
{
 int id;
 char name[20];
};
void test();
struct Stu
{
 int id;
 char name[20];
};
void test();
struct Stu
{
 int id;
 char name[20];
};
void test();
struct Stu
{
 int id;
 char name[20];
};

int main()
{
 
 return 0;
}
如果直接这样写,test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷⻉5份在test.c中。
如果test.h ⽂件⽐较⼤,这样预处理后代码量会剧增。如果⼯程⽐较⼤,有公共使⽤的头⽂件,被⼤家
都能使⽤,⼜不做任何的处理,那么后果真的不堪设想。
如何解决头⽂件被重复引⼊的问题?答案:条件编译。

解决办法如下:

每个头⽂件的开头写:
#ifndef __TEST_H__   //__TEST_H__是根据头文件文件名来取的
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或者
#pragma once

十三、其他预处理指令

#error
#pragma
#line
#define
#include
#ifdef
#elif
#undef
...
不做介绍,⾃⼰去了解。
#pragma pack()在结构体部分介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2124649.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红米K60U/K50/Note11TPro澎湃OS无法绑定账号解锁BL-不能激活小米账号

小米澎湃OS对于解锁BL&#xff0c;新增了各种限制&#xff0c;早前我们还能使用bypass脚本来实现澎湃OS上绑 定账号成功&#xff0c;但随着澎湃OS七月系统上的推送&#xff0c;旧版的bypass已经彻底失效&#xff0c;并且无法安装 旧版的设置APK来解决问题。此次涉及的机型有红米…

SpringSecurity剖析

1、SpringSecurity 入门 1.1、简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。Spring Security是一个框架&#xff0c;致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样&#xff0c;Sp…

【PX4-AutoPilot教程-TIPS】PX4中MAVLink话题频率修改

PX4中MAVLink话题频率修改 方法一&#xff1a;使用QGC地面站通过命令行解释器MAVLink Shell修改话题频率方法二&#xff1a;使用SD卡中的命令脚本文件修改话题频率方法三&#xff1a;通过修改PX4飞控固件源码修改话题频率 环境&#xff1a; PX4 &#xff1a;1.13.0 方法一&am…

SOP流程制定:vioovi ECRS工时分析软件的智慧引领

在现代制造业中&#xff0c;标准化操作流程&#xff08;SOP&#xff09;已成为提升生产效率、确保产品质量、降低运营成本的关键要素。SOP不仅为生产活动提供了明确的指导&#xff0c;还促进了企业管理的规范化和精细化。然而&#xff0c;如何科学、高效地制定SOP流程&#xff…

CISC 和 RISC 架构的对比

研究 RISC 架构优缺点的最简单方法是将其与其前身进行对比&#xff1a; CISC&#xff08;复杂指令集计算机&#xff09;架构。 内存中的两个数字相乘 右图表示一台普通计算机的存储方案。 主存储器被划分为编号从&#xff08;行&#xff09;1&#xff1a;&#xff08;列&…

RAG系统的7个检索指标:信息检索任务准确性评估指南

大型语言模型&#xff08;LLMs&#xff09;作为一种生成式AI技术&#xff0c;在近两年内获得了显著的关注和应用。但是在实际部署中&#xff0c;LLMs的知识局限性和幻觉问题仍然是一个挑战。检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09…

好网站包含哪些方面

好网站通常在多个方面都表现出色&#xff0c;包括但不限于设计、内容、导航、性能和互动性。下面将详细介绍这些方面。 首先&#xff0c;设计是一个网站吸引用户的第一印象。一个好的网站设计应该是清晰、直观、美观&#xff0c;并且符合用户体验原则。页面布局应该合理&#x…

Spire.PDF for .NET【文档操作】演示:创比较 PDF 文档

PDF 已成为跨不同平台共享和保存文档的标准格式&#xff0c;在专业和个人环境中都发挥着无处不在的作用。但是&#xff0c;创建高质量的 PDF 文档需要多次检查和修订。在这种情况下&#xff0c;了解如何有效地比较 PDF 文件并找出它们的差异变得至关重要&#xff0c;这使文档编…

【Go】Go语言基本语法--注释、变量、常量

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Memfire Cloud使用技巧:让开发更简单,更高效

在软件开发的世界里&#xff0c;Memfire Cloud就像是一位隐形的助手&#xff0c;它悄无声息地帮助开发者们解决了一个又一个难题。如果你还在为搭建服务、开发API接口而头疼&#xff0c;那么Memfire Cloud无疑是你的救星。今天&#xff0c;我们就来聊聊如何使用Memfire Cloud&a…

面试官:v-if和v-for的优先级是什么?

一、作用 v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法&#xff0c;其中 items 是源数据数组或者对象&#xff0c;而 item 则是被迭代的…

【人工智能学习笔记】4_4 深度学习基础之生成对抗网络

生成对抗网络&#xff08;Generative Adversarial Network, GAN&#xff09; 一种深度学习模型&#xff0c;通过判别模型&#xff08;Discriminative Model&#xff09;和生成模型&#xff08;Generative Model&#xff09;的相互博弈学习&#xff0c;生成接近真实数据的数据分…

Rust程序结构与代码注释

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 3.1 Rust程序结构 我们从一个最简单的程序入手&#xff0c;来观察一个Rust的程序结…

Express框架中的res

中间件由两部分组成,中间件方法和请求处理函数; 中间件方法由express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求; app.get(‘请求路径’,‘处理函数’); //接收并处理get请求 app.post(‘请求路径’,‘处理函数’); //接收并处理post请求 可以对同一个请求…

盘点ai可以变现的3大生意,看看你猜到了几个?

一、做动物ai视频&#xff0c;通过流量变现 最近各个视频平台都很火的猫meme&#xff0c;不仅在各个蓝v账号混的风生水起&#xff0c;也是很多文旅的爆款密码。 有很多个人账号也会做比如动物跳舞&#xff0c;做饭的视频&#xff0c;通过流量收入和接广告来变现。 ** 二、小说…

【OJ刷题】双指针问题

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;OJ刷题入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 目录 1…

【NOI-题解】1272. 郭远摘苹果1274. 求各个科目成绩的平均分1275. 输出杨辉三角的前N行1496. 地雷数量求解

文章目录 一、前言二、问题问题&#xff1a;1272. 郭远摘苹果问题&#xff1a;1274. 求各个科目成绩的平均分问题&#xff1a;1275. 输出杨辉三角的前N行问题&#xff1a;1496. 地雷数量求解 三、感谢 一、前言 欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&…

《旧衣服的销路在哪里》

在当今社会&#xff0c;随着人们生活水平的不断提高&#xff0c;衣物的更新换代速度愈发加快&#xff0c;大量的旧衣服亟待处理&#xff0c;旧衣服回收市场由此应运而生&#xff0c;且发展态势日益蓬勃。 旧衣服回收的类型丰富多样。从材质上区分&#xff0c;有柔软的棉质、透…

从代理协议的角度出发:解锁住宅代理

在各类代理中&#xff0c;代理协议扮演着至关重要的角色&#xff0c;它规定了代理运作的规则和要求。和其他代理相同&#xff0c;住宅代理也依赖于多种代理协议来处理不同类型的流量。在本文中&#xff0c;我们将深入研究HTTP和HTTPS代理的工作原理&#xff0c;比较它们的差异&…

IDC基础学习笔记

一、数据中心介绍 1、数据中心级别划分&#xff1a; 2、数据中心结构&#xff1a; 3、IT系统组成 二、数据中心硬件知识 1、服务器组件 服务器的正面接口&#xff1a; 服务器的反面接口&#xff1a; &#xff08;1&#xff09;CPU CPU定义&#xff1a;中央处理器&#xff08…