C语言——预处理详解(上)

news2024/9/22 10:05:50

目录

引言

预定义符号

define 定义常量

#define 定义宏

带有副作用的宏参数

宏替换的规则

宏和函数的对比


引言

在C语言编程中,预处理是编译前的关键步骤,它负责处理如宏定义、条件编译和文件包含等指令。今天我们来学习一下有关C语言——预处理的内容。

预处理部分分两篇。

预定义符号

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

__FILE__    //进行编译的源文件
__LINE__    //文件当前的行号
__DATE__    //文件被编译日期
__TIME__    //文件被编译那一瞬的时间
__STDC__    //如果编译器遵循ANSI C(标准C),其值为1,否则未定义(报错)

举个例子:

#include<stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	return 0;
}

运行结果:

define 定义常量

基本语法:

#define name stuff

举个例子:

#define MAX 100
#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__ )

1.定义了一个常量 MAX ,值为100。

2.为 register 关键字创建别名 reg 。

3.定义了一个无限循环的do_forever。

4.在 switch 语句的 case 前自动添加 break ,但是最好不要这样子使用。

5.续行符可以防止分行后出现问题。

接下来我们来思考一下:

在define定义标识符的时候,要不要在最后加上 ;
比如这样:

#define MAX 1000;
#define MAX 1000

来看看如下场景:

#define MAX 1000;
int main()
{
	int max = 0;
	if (1)
		max = MAX;
	else
		max = 1;
	return 0;
}

像在这段代码中,MAX相当于是 1000;  ,代码就相当于变成了这种:

#define MAX 1000;
int main()
{
	int max = 0;
	if (1)
		max = MAX;
        ;
	else
		max = 1;
	return 0;
}

这样子最终导致了 else 缺少 if 与其配对。

因此,建议在define定义标识符的时候,最好不要在最后加上 ; 。

#define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为定义宏

下面是宏的声明方式:

#define name(parameters) stuff

这里的 name 是你定义的宏的名称, parameters 是宏的参数列表(可以是空的,也可以包含一个或多个参数),而 stuff 是宏展开时将要替换成的文本。如果 stuff 中包含了 parameters 中的参数,那么这些参数在宏展开时会被实际的参数值所替换。

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

举个例子:

//实现一个宏,计算一个数的平方
#define SQUARE(x) x*x

int main()
{
	int a = 5;
	printf("%d\n", SQUARE(a + 1));
	return 0;
}

按照我们的思考逻辑,答案显然是36,但是我们运行之后会发现:

答案居然是11?!

为什么会出现这种情况呢?问题就出在宏上。

我们知道宏是直接替换的,上面的代码经过替换后就变成了如下形式:

printf("%d\n", a+1*a+1);

这样子算出来的结果就符合运算结果了。

为了避免出现这种情况,我们可以对宏进行如下修改:

#define SQUARE(x) (x)*(x)

这样子就符合预期了

我们接下来再来看一个示例:

#define DOUBLE(x) (x) + (x)

int main()
{
	int a = 5;
	printf("%d\n", 5 * DOUBLE(a));
	return 0;
}

这个的运算结果为:30

这显然也是不符合我们的预期的。上面的代码经过替换后变成:

printf("%d\n", 5 * 5 + 5);

解决方法如下:

#define DOUBLE(x) ((x) + (x))

由此得出:在使用宏时一定不要吝啬括号,该加就加,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

带有副作用的宏参数

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

例如:

x + 1; //不带副作用
x++;  //带副作用

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

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

int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d, y=%d, z=%d\n", x, y, z);
	return 0;
}

运行结果如下:

我们来分析一下:

z = ((x++) > (y++) ? (x++) : (y++))

1.首先判断:x++ 与 y++ ,由于是后置++,判断时 x 为 5,y 为 8,8 > 5。

2.判断完后x自增1变为6,y自增为9。

3.接着执行 y++,由于是后置++,返回结果为9。

4.再接着 y 进行自增,y 最终结果为10。

当我们向宏中传递有副作用的参数,而并且参数在宏中出现了不止一次,那么该参数的副作用也不止一次。

为了避免这种问题,最好不要在宏的参数中使用带有副作用的表达式(如递增或递减操作符)。

宏替换的规则

在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤:

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

来看个简单的例子:

#include<stdio.h>

#define a 100
int main()
{
    printf("%d\n",a);
    return 0;
}

在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

注意:
宏参数 和 #define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

宏里面是可以嵌套宏的,例如这样:

MAX(x, MAX(2, 3))

递归宏在宏定义中通常不推荐使用,且容易出错

当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。

举个例子:

#include <stdio.h>    
    
#define HELLO "Hello, "    
#define WORLD "world!"    
    
int main() 
{    
    // 字符串常量中的HELLO和WORLD不会被预处理器替换,因为它们不是作为printf的参数传递的  
    printf("这是一个测试:HELLO WORLD\n"); // 输出: 这是一个测试:HELLO WORLD    
      
    // 正确的做法是使用格式化字符串和多个参数,此时HELLO和WORLD会被替换  
    printf("这是一个测试:%s%s\n", HELLO, WORLD); // 输出: 这是一个测试:Hello, world!    
    
    return 0;    
}

输出结果如下:

宏和函数的对比

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

例如这样:

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

宏和函数都能完成需求,为什么说在执行简单的运算这一需要时,宏更加有优势呢?

原因如下:

1.用于 调用函数从函数返回的代码 可能比实际执行这个小型计算工作所需要的时间更多。所以 宏 比 函数 在程序的规模和速度方面更胜⼀筹
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏的参数是类型无关的

似乎宏相较于函数更加厉害,然而,宏并不适合做复杂、大的运算。

相较于函数,宏也有很多劣势:

1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
2. 宏是没法调试的
3. 宏由于类型无关,也就不够严谨
4. 宏可能会带来运算符优先级的问题,导致程序容易出现错误。

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

例如这样:

int* p = (int*)malloc(5 * sizeof(int));

我们觉得这样子使用malloc函数太麻烦了,我们可以这样子:

Malloc(5, int);

函数显然是不可以做到的:函数不能传递类型。

宏是可以做到的,宏能接收类型。

#define MALLOC(num, type) ((type*)malloc(num * sizeof(type)))  

int main()
{
    int* p = MALLOC(5, int);
    if (p == NULL) 
    {
        perror("malloc fail:");
        return 1;
    }
    free(p);
    return 0;
}

宏和函数的对比表格:

属性#define 定义宏函数
代码长度

每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。

函数代码值出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,相对慢点
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则临近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多加括号函数参数只有在函数调用的时候求值一次,它的结果值传递给函数,表达式的求值结果更容易预测
带有副作用的参数参数可能被替换到宏中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预估的结果函数参数只有在传参时调用一次,结果更容易预测
参数类型宏的参数与类型无关,只要对参数的操作是合法的,他就可以使用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的
调试宏不方便调试函数可以逐语句调试
递归宏不能递归函数可以递归

———————————————————————————————————————————

以上是预处理的部分内容,由于篇幅原因,欢迎大家去查看预处理的下部分——C语言——预处理详解(下)

希望大家能点赞收藏支持下!!!

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

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

相关文章

洛谷 3道 函数 题目 题解 (超详细)

题目目录&#xff1a; No.1 B2137 判决素数个数 No.2 B2138 最大质因子序列 No.3 B2140 二进制分类 OK&#xff0c;开始正文&#xff01; 第一题&#xff1a; B2137 判决素数个数 题目描述 求 X&#xff0c;Y 之间的素数个数&#xff08;包括 X 和 Y&#xff09;。 输入…

LB-8100A 推拉力测试机精密推拉力试验机

LB-8100A 多功能推拉力测试机广泛应于与 LED 封装测试、IC 半导体封 装测试、TO 封装测试、IGBT 功率模块封装测试、光电子元器件封装测试、汽 车领域、航天航空领域、军工产品测试、研究机构的测试及各类院校的测试 研究等应用。 多功能推拉力测试机精密推拉力试验机 *设备硬件…

地热模拟软件opengeosys-OGS安装和学习1

1.下载地址 官网&#xff1a;https://www.opengeosys.org/ 界面Gina&#xff1a;https://discourse.opengeosys.org/t/gina-version-3-24/175 https://teambeam.bgr.de/my/drive/folder/68&#xff08;注意下载压缩包&#xff0c;有些注册表需要处理&#xff09; 2.处理 下…

视频汇聚平台智能边缘分析一体机分析平台摄像头异常位移算法识别检测

智能边缘分析一体机在摄像头异常位移检测方面扮演着关键角色&#xff0c;它利用先进的图像处理技术和机器学习算法来实时监测摄像头状态&#xff0c;判断是否发生了非预期的位移。下面是智能边缘分析一体机如何检测摄像头异常位移的详细步骤&#xff1a; 1. 图像帧对比&#x…

内部排序(二路归并、基数、计数)

【内部排序&#xff08;插入、交换、选择&#xff09;】 一、二路归并排序 1. 算法思想与实现步骤 1&#xff09;算法思想&#xff1a; 二路归并排序是一种分治算法。它将待排序的序列分为两个子序列&#xff0c;分别对这两个子序列进行排序&#xff0c;然后将两个已排序的子…

安美数字酒店宽带运营系统 weather.php 任意文件读取漏洞复现

0x01 产品简介 HiBOS酒店宽带运营系统是由安美世纪(北京)科技有限公司开发的一套专为酒店设计的宽带管理系统。该系统旨在提升酒店宽带服务的运营效率和安全性&#xff0c;为酒店客人提供稳定、高速、便捷的上网体验。 0x02 漏洞概述 安美数字酒店宽带运营系统 weather.php …

【Linux】快速入门系列(四) —— Linux实用操作

Linux实用操作 前言&#xff1a;先换个阿里的源一、各类小技巧 — 快捷键&#xff08;一&#xff09;强制停止 CtrlC&#xff08;二&#xff09;退出或登出 CtrlD&#xff08;三&#xff09;历史命令搜索的三种方式&#xff08;四&#xff09;光标移动快捷键&#xff08;五&…

无人机之机架布局篇

一、“十”字型 “十”字布局适合刚接触无人机的初学者&#xff0c;是起初最先发展的布局。 优点&#xff1a;控制简单&#xff0c;首尾明确&#xff0c;俯仰和横滚运动仅需改变一对电机的转速。 缺点&#xff1a;机体前向视角易被螺旋桨遮挡&#xff0c;灵活性有所不足&…

DHCP服务(服务名dhcpd,端口UDP /67和UDP/68)

目录 前言 配置文件 DHCP服务器的配置 下载安装DHCP服务 编辑配置文件 重启服务 客户端测试 配置客户端网卡 重启网卡 前言 DHCP动态主机配置协议是一种网络协议&#xff0c;提供了动态配置IP地址的功能&#xff0c;允许服务器自动为网络上的设备分配IP地址和其他网络…

自动化测试面试常用题库

自动化面试题记录整理&#xff08;部分答案自己整理&#xff09; selenium中如何判断元素是否存在&#xff1f; 没有提供原生的方法判断元素是否存在&#xff0c;一般我们可以通过定位元素异常捕获的方式判断selenium中hidden或者是display &#xff1d; none的元素是否可以定…

8080端口被占怎么处理?

一、Windows系统 1、 按住WinR输入CMD打开命令行窗口 2、运行以下命令来查看占用端口 8080 的程序的 PID&#xff08;进程标识符&#xff09;&#xff0c;可以看到PID是12040 netstat -ano | findstr 8080 3、 运行以下命令来终止占用端口 8080 的程序&#xff1a; taskkill /p…

精彩回顾 | 风丘科技亮相2024名古屋汽车工程博览会

2024年7月17日-19日&#xff0c;风丘科技联合德国IPETRONIK亮相日本名古屋汽车工程博览会。该展会面向汽车行业不同应用场景&#xff0c;包括新的eAxle、FCEV、ADAS、测试测量系统和ECU测试等相关技术&#xff0c;是一个专为活跃在汽车行业前线的工程师和研究人员举办的汽车技术…

腾讯云 AI代码助手 | 尽享 AI时代下的程序员福利

腾讯云 AI代码助手 | 尽享 AI时代下的程序员福利 前言腾讯云AI代码助手智能补全代码信息&#xff08;代码补全&#xff09;精准修复错误代码&#xff08;代码优化&#xff09;清晰解释既有代码&#xff08;解释代码&#xff09;按需生成单元测试&#xff08;生成单元测试&#…

NoSQL 之Redis集群模式

目录 案例概述 redis工作模式 主从模式 哨兵模式 redis cluster模式 Redis集群介绍 Redis集群的优势 Redis集群的实现方法 Redis-Cluster数据分片 Redis-Cluster的主从复制模型 Redis集群部署 案例部署 安装redis 检查redis的状态 修改配置文件 重启启动redis服…

第二证券:A股三大指数震荡调整 环保板块强势拉升

环保板块迎来重磅方针 昨日&#xff0c;环保板块强势拉升&#xff0c;永清环保20%涨停&#xff0c;东江环保、启迪环境、雪迪龙等多股涨停。 音讯面上&#xff0c;中共中央、国务院近来印发《关于加快经济社会开展全面绿色转型的定见》。定见布置加快形成节省资源和保护环境的…

WordPress原创插件:Keyword-ranking-seo 1.0 关键词排名插件 有利于seo

WordPress原创插件&#xff1a;Keyword-ranking-seo 1.0 关键词排名插件 有利于seo 当用户访问网站时&#xff0c;该链接会随机选择一个关键词&#xff0c;并使用选定的搜索引擎进行搜索。 插件下载链接 https://download.csdn.net/download/huayula/89632792

备战秋招60天算法挑战,Day17

题目链接&#xff1a; https://leetcode.cn/problems/valid-anagram/ 视频题解&#xff1a; https://www.bilibili.com/video/BV1db421J7qK/ LeetCode 242. 有效的字母异位词 题目描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意…

同城搭子社交系统开发同城搭子群活动APP圈子动态小程序

引言 随着互联网技术的飞速发展&#xff0c;同城搭子社交系统作为一种新兴的社交模式&#xff0c;正逐渐在市场中占据一席之地。该系统通过搭子群活动和圈子动态等功能&#xff0c;为用户提供了一种高效、精准的社交体验。本文将从市场前景、使用人群、盈利模式以及运营推广等…

azure 上如何创建 Kubernetes 集群?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

SO_REUSEADDR 和 SO_REUSEPORT 的区别 / Linux TCP SO_REUSEPORT — 使用和实现 ……

注&#xff1a;机翻&#xff0c;未校。 Difference Between SO_REUSEADDR and SO_REUSEPORT Last Updated : 05 Feb, 2023 Processes use sockets as endpoints of a two-way channel to transfer data. The socket options SO_REUSEADDR and SO_REUSEPORT have different ma…