【C语言进阶】程序编译中的预处理操作

news2024/12/25 12:23:49

📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL..

📚以后会将数据结构收录为一个系列,敬请期待

● 本期内容讲解C语言中程序预处理要做的事情

目录

1.1 预处理符号

1.2 #define

1.2.1  #define定义标识符

1.2.2 #define定义宏

1.2.3 #define替换规则

1.3 #和## 

1.4 带副作用的宏参数 

1.5 宏与函数的区别与联系


1.预处理详解

1.1 预处理符号

__FILE__     //进行编译的源文件

__LINE__    //文件当前的行号

__DATE__  //文件被编译的日期

__TIME__   //文件被编译的时间

__STDC__  //如果编译器遵循ANSIC,其值为一,否则未定义

打印出来看一看,很明显,vs2019不支持ANSIC 

1.2 #define

1.2.1  #define定义标识符

 语法:#define name stuff 

#define MAX 100     //定义MAX的值为100
#define reg register //为register这个关键字创建简短的名字
#define ro for(;;)   //打印for这个死循环
#define CASE break;case  //在case语句后换成break;case
#define debug_print printf("file:%s\tline:%d\t\
							 date:%s\ttime:%s\t",__FILE__,__LINE__,__DATE__,__TIME__);   
#include<stdio.h>
int main()
{
	printf("%d\n", MAX);
	debug_print;
	//ro
	//{
	//	//printf("hehe\n");
	//}
	//int a = 0;
	//switch (a)
	//{
	//case1:  //不推荐这种写法,可读性太差
	//CASE2:
	//CASE3:
	//}
	return 0;
}

提问:

 在定义#define的时候,是否要加上;号

比如:

#define MAX1 100
#define MAX2 100;
#include<stdio.h>
int main()
{
	printf("%d\n", MAX1);
	printf("%d\n", MAX2);
	return 0;
}

究竟是MAX1可以还是MAX2可以呢?看看运行结果

解析:实际上在预处理的时候,会将#define定义的标识符进行替换,直接替换成表达式。比方说,MAX1处会被直接替换为100,MAX2处会被替换为100;在这个地方语句就出问题了,怎么两个分号呢?所以MAX2是不能这么写的。

1.2.2 #define定义宏

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

宏的定义:

#define name(parament-list) stuff 

如果两者之间有任何空白存在,那么参数列表就会解释为stuff的一部分

举个例子:

#define SQUARE(x) x*x
#include<stdio.h>
int main()
{
	int a = 5;
	int c = SQUARE(a);
	printf("%d\n", c);
	return 0;
}

上述代码输出一定是25,因为在预处理过程中会直接替换,int c = a*a ,那么我再问问大家,如果我传的是a+5呢? 

#define SQUARE(x) x*x
#include<stdio.h>
int main()
{
	int a = 5;
	int c = SQUARE(a+5);
	printf("%d\n", c);
	return 0;
}

 

解析:为什么我传递的是a+5是10,应该是输出100的,为什么最后打印了一个35?原因是这样的,还是替换 int c = a+5*a+5 ,输出就是35,怎么解决呢,要加括号。 

加上括号之后,就对了。

但是呢,我又突发奇想 ,又问大家一个问题,看下面这个代码,会输出什么?

#define DOUBLE(x) (x)+(x)
#include<stdio.h>
int main()
{
	int a = 5;
	int c = 10*DOUBLE(a + 5);
	printf("%d\n", c);
	return 0;
}

按理来说,调用DOUBLE这个宏应该是2x也就是20,再乘10就是200。那我们来看看是否是输出200呢?

 解析:可以看到输出的是110,并不是200,为什么呢?其实上就是替换 int c = 10*(a+5)+(a+5),实际上是输出110而不是我们想要的200,怎么办呢?其实还是加括号,再宏定义处再加括号即可

所以呢在创建宏的时候,一定要不要吝啬括号,一定要多加括号。

1.2.3 #define替换规则

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

1、在调用宏时,首先对参数进行检查,看看是否任何由#define定义的符号。如果是,它们首先被替换。

2、替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换

3、最后,再次对结果文件进行扫描,看看他是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

1、宏函数和#define定义中可以可以出现其他#define定义的符号。但是对于宏,不可以递归

2、当预处理器搜索宏的时候,字符串常量不被搜索。

可以看个例子:

#define DOUBLE(x) ((x)+(x))
#include<stdio.h>
int main()
{
	printf("DOUBLE(5)");
}

 

1.3 #和## 

(1)首先我们一起来看这样一段代码

#include<stdio.h>
int main()
{
	char* p = "hello bit\n";
	printf("%s", p);
	printf("hello ""bit");
	return 0;
}

 

可以看到,字符串是有自动连接的特点的。那么我有个设想,是说我们可以根据数据类型来打印对应的数据,比方说我给一个宏传 3 整形,他就可以给我打印出3;比方说我给一个宏传3.5这个浮点型,就可以给我打印出3.500000,看我如何实现

#define PRINT(FORMAT,VALUE) printf("the value of "#VALUE" is "FORMAT"\n",VALUE);
#include<stdio.h>
int main()
{
	int a = 10;
	//printf("the value of a is %d\n", a);
	PRINT("%d",a);
	return 0;
}

这个代码是可以这么理解,就是宏参数前面加上#号,可以把一个宏参数变成对应的字符串。

这个代码的输出结果就是,PRINT("%d",a)会被替换为printf("the value of "a" is "%d"\n",10)

 (2)##

##的作用是:

1、##可以把位于它两边的符号合成一个符号

2、它允许宏定义从分离的片段创建标识符

 举个栗子:

#include<stdio.h>

#define ping(num1,num2) num1##num2 
int main()
{
	int chinano1 = 2024;
	int r = ping(china,no1);
	printf("%d", r);
	return 0;
}

 

解析:宏会直接替换,int r = chinano1,所以最后会打印出2024。

1.4 带副作用的宏参数 

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

副作用是什么呢?例如:

int x = 5;
//我想让y变成6,有几种方法
//1、
int y = x+1 //无副作用
//2、
int y = ++x; //有副作用的

举个栗子来证明下如果是带副作用的宏参数,会有什么问题:

#include<stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
	//传递有副作用的宏参数
	int a = 5;
	int b = 6;
	int c = MAX(a++,b++);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("c = %d\n", c);
	return 0;
}

输出结果:

 

为什么是这个结果,不是6和7比较返回7吗?其实不是的,因为你调用了宏就是替换,

int c = (a++)>(b++)?(a++):(b++),可以看到,宏参数每往后走一步就加加,如果一直反复调用实际上是很危险的。

1.5 宏与函数的区别与联系

属性#define定义的宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,其他的代码量会大幅增长函数代码只出现在一个地方,每次调用,都去那一个地方找
执行速度更快存在函数的调用与返回,要慢一些
操作符优先级宏的求值是要在上下文联系中的,除非加上括号,否则会产生不可改变的后果函数参数只在函数调用的时候求值一次,函数表达式结果更好预测
带有副作用的参数参数可能被替换到任意位置,所以带有副作用的参数会带来不可预料的结果

函数参数只在传参的时候求值一次,因此表达式结果更好预测

参数类型宏的参数与类型无关,只要表达式合法,都可以用函数参数对类型要求十分严格,如果函数类型不同,就需要不同的函数参数,即使他们执行的类型是相同的。
调试宏是不方便调试函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

 


  总结

上文就是C语言编程预处理阶段的相关知识了,C语言进阶系列正式完结!!!

如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。

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

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

相关文章

【Docker学习】docker stats

命令&#xff1a; docker container stats 描述&#xff1a; 显示容器资源使用的状态&#xff08;实时&#xff09; 用法&#xff1a; docker container stats [OPTIONS] [CONTAINER...] 别名&#xff1a; docker stats(docker的一些命令可以简写&#xff0c;docker stats就等同…

Odoo17开发环境搭建

1.先下载godoo17_20240227_02.zip压缩包&#xff0c;里面包含了项目用到的所有的插件了&#xff0c;直接使用这个包即可。 下载地址&#xff1a;https://download.csdn.net/download/java173842219/89242257 2.解压该压缩包&#xff0c;目录如下&#xff1a; 3.下载pycharm并…

如何保证Redis双写一致性?

目录 数据不一致问题 数据库和缓存不一致解决方案 1. 先更新缓存&#xff0c;再更新数据 该方案数据不一致的原因 2. 先更新数据库&#xff0c;再更新缓存 3. 先删除缓存&#xff0c;再更新数据库 延时双删 4. 先更新数据库&#xff0c;再删除缓存 该方案数据不一致的…

声明式事务(@Transactional)使用时需要注意的坑

前言 上两篇文章已经详细分析了申明式事务的实现原理&#xff0c;知道了底层原理之后&#xff0c;现在就可以开始使用申明式事务去简化我们的代码了。但是在使用Transactional注解的时候也会经常遇到一些问题&#xff0c;有些问题不仔细测试观察的话还不容易发现&#xff0c;比…

Windows vbs脚本定时给焦点窗口发送消息

直接上脚本代码,你们可以自己看着改 MsgInputbox("message1") Msg1Inputbox("message2") numInputbox("number")a1 bnumset wshshellCreateObject("wscript.shell") 创建Windows的shell对象打开shell窗口 wscript.sleep 5000for ia t…

vue本地调试devtools

一、谷歌浏览器加载扩展程序 二、把解压的压缩包添加即可&#xff0c;重启浏览器 三、启动前端本地项目&#xff0c;即可看到Vue小图标

Linux|awk 特殊模式“BEGIN 和 END”

引言 在本文[1]&#xff0c;我们将介绍Awk的更多特性&#xff0c;特别是两个特殊的模式&#xff1a;BEGIN和END。 这些独特的功能在我们努力扩展和深入探索构建复杂Awk操作的多种方法时&#xff0c;将大有裨益。 实例 让我们从Awk系列的开篇回顾开始&#xff0c;回想一下&#…

SSL证书一般是怎么收费的?

SSL证书的费用通常按照以下几个因素决定&#xff1a; 1. 证书类型&#xff1a; - 域名验证&#xff08;DV&#xff09;证书&#xff1a;这是最基本的类型&#xff0c;仅验证域名所有权&#xff0c;费用一般在几十到几百之间. - 组织验证&#xff08;OV&#xff09;证书&#xf…

【触摸案例-手势解锁案例-九宫格 Objective-C语言】

一、手势解锁案例,九宫格,我们先来分析一下怎么实现: 首先呢,我们先来运行一下, 这一块儿,上面的这九个东西,肯定是要有一个九宫格的一个算法的问题,然后呢,上边的这九个小圆圈儿,这是什么东西,Button,为什么是Button,因为可以点,是吗,就因为这个?实际上,你用…

LeetCode55:跳跃游戏

题目描述 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 解题思想 每次…

软考-信息系统项目管理师-论文技术架构模板(60天备考第26天)

分享一段信息系统项目管理师论文项目技术架构描述的万能模板&#xff0c;供大家参考。距离考试还有二十八天&#xff0c;如果论文写不好的可以加微进论文指导群学习论文写作。 该系统前端基于Vue开发&#xff0c;后端基于java开发&#xff0c;前后端分离部署。整体采用B/S架构&…

【webrtc】MessageHandler 5: 基于线程的消息处理:以PeerConnection信令线程为例

peerconn的信令是通过post 消息到自己的信令线程消息来处理的PeerConnectionMessageHandler 是具体的处理器G:\CDN\rtcCli\m98\src\pc\peer_connection_message_handler.hMachinery for handling messages posted to oneself PeerConnectionMessageHandler 明确服务于 signalin…

公网ip申请ssl仅260

现在很多网站都已经绑定域名&#xff0c;因此使用的都是域名SSL证书保护网站传输信息安全&#xff0c;而没有绑定域名只有公网IP地址的网站想要保护传输信息安全就要申请IP SSL证书。IP SSL证书也是由正规CA认证机构颁发的数字证书&#xff0c;用来保护用户的隐私以及数据安全&…

容器工作流

背景 目前某平台使用计算容器和解析容器&#xff0c;这两种容器目前通过rabbitmq消息来进行链接&#xff0c;形成容器工作流&#xff0c;使用容器工作流框架可以省去两个容器中间环节的控制&#xff0c;不需要再使用java代码对容器的操作&#xff0c;通过容器工作流框架即可控…

资深项目经理15年心得:管理需求变更5大技巧

高效管理需求变更对项目管理至关重要。通过严格的变更控制&#xff0c;确保所有需求变更都与项目目标和范围保持一致&#xff0c;避免偏离原定计划&#xff0c;有助于项目按既定目标顺利推进。能够及时评估变更对项目的影响&#xff0c;有利于减低项目延期和超支的风险&#xf…

特斯拉PIXCELL矩阵大灯擎耀远程控制技术照亮未来智能之光

在科技的浪潮中&#xff0c;特斯拉这个名字如同一道闪电&#xff0c;照亮了新能源汽车的天空。而在这片星空中&#xff0c;特斯拉PIXCELL矩阵大灯则如同一颗璀璨的星辰&#xff0c;以其独特的创新技术和卓越的性能&#xff0c;为驾驶者提供了前所未有的照明体验。矩阵大灯技术如…

OceanBase开发者大会实录-杨传辉:携手开发者打造一体化数据库

本文来自2024 OceanBase开发者大会&#xff0c;OceanBase CTO 杨传辉的演讲实录—《携手开发者打造一体化数据库》。完整视频回看&#xff0c;请点击这里&#xff1e;> 各位 OceanBase 的开发者&#xff0c;大家上午好&#xff01;今天非常高兴能够在上海与大家再次相聚&…

Android View事件分发面试问题及回答

问题 1: 请简述Android中View的事件分发机制是如何工作的&#xff1f; 答案: 在Android中&#xff0c;事件分发机制主要涉及到三个主要方法&#xff1a;dispatchTouchEvent(), onInterceptTouchEvent(), 和 onTouchEvent(). 当一个触摸事件发生时&#xff0c;首先被Activity的…

广交会烹饪机器人用上大模型 支付宝小程序云提供技术支持

近日&#xff0c;第135届广交会正在火热进行&#xff0c;记者获悉&#xff0c;支付宝小程序云助力合作伙伴田螺云厨&#xff0c;在烹饪机器人上开始用上大模型技术。各类智能产品的亮相&#xff0c;从中国制造迈向中国创造&#xff0c;也成为广交会的一个亮点。 &#xff08;图…

HR招聘选拔,什么是人才测评方法?

人才测评方法是人才测评系统的一个构成部分&#xff0c;在一个系统指引之下&#xff0c;研究人员会根据具体的情况&#xff0c;选择出一套合适的人才测评方法&#xff0c;以便于系统更好地实现目标。每一种人才测评方法&#xff0c;都有自己的适用范围&#xff0c;企业工作者须…