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

news2024/11/26 10:34:46

文章目录

  • 前言
  • 一、预定义符号
  • 二、#define定义标识符
  • 三、#define定义宏
  • 四、#define的替换规则
  • 五、带有副作用的宏
  • 六、宏和函数的对比
  • 七、#undef的作用
  • 八、# 和
    • #的作用
    • ##的作用
  • 总结


前言

  C语言的入门学习差不多要到尾声了,感觉如何呢~
  前文说编译的第一步就是预编译(即预处理),那具体这个阶段会做哪些工作呢?

正文开始!


一、预定义符号

  在C语言中,有一些有意思的预定义符号,这些预定义符号都是语言内置的,即以及定义好的,我们可以直接使用。预定义符号主要有以下几个:

// 实际项目可能会有用
__FILE__        //进行编译的源文件
__LINE__        //文件当前的行号
__DATE__        //文件被编译的日期
__TIME__        //文件被编译的时间
__FUNCTION__    //进行编译的函数
__STDC__        //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号是已经用#define定义好的,在代码运行后的预处理阶段会被替换为相应的内容

其使用就是直接打印即可,只要注意占位符是 %s 还是 %d 即可:

#include <stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __FUNCTION__);
	//printf("%d\n", __STDC__); // VS2022不支持
	
	return 0;
}

运行结果如下:
在这里插入图片描述

二、#define定义标识符

用#define定义标识符的格式如下:

#define MAX 100
#define reg register //懒人觉得register太长了

这些被#define定义的标识符都将在预处理阶段被编译器替换成对应的内容

我产生了一些联想,事实上每个初学者经常会犯以下错误:
main->mian;,->,;(->( ;true->ture

于是我们将错就错,有了以下解决方案

#define mian main
#define ,
#define (
#define )
#define ture true
#define ;

开玩笑的,我们还是要从源头上解决,多敲多练,尽可能避免这种一般性的低级错误

三、#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
其声明方式为:#define name( parament-list ) stuff,其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

#include <stdio.h>
#define SQUARE(x) x*x //求x的平方

int main()
{
	int ret = SQUARE(5);
	//相当于int ret = 5*5,在预处理阶段就被展开了;
	printf("%d\n", ret); //结果为25
	
	return 0;
}

意思是这么个意思,但是我们要注意这个声明是不太正确的,我们不能在宏的使用里吝啬括号,不然可能会发生以下错误:

printf(“%d\n” ,SQUARE( 5 + 1) );
展开为:printf(“%d\n” ,5 + 1 * 5 + 1);
结果是打印11,与我们想要的25相悖
正确声明是:#define SQUARE(x) ((x)*(x))

也就是说用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用

四、#define的替换规则

  以下面代码为例,现在我们来讲解下#define的替换规则

#include <stdio.h>
#define MAX 100
#define SQUARE(x) ((x)*(x)*MAX)

int main()
{
	int ret = SQUARE(5);
	printf("%d\n", ret);
	return 0;
}
  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换

#define定义的宏中含有#define定义的符号MAX,则调用该宏时,首先将MAX替换

#include <stdio.h>
#define SQUARE(x) ((x)*(x)*100)
int main()
{
	int ret = SQUARE(5);
	printf("%d\n", ret);
	return 0;
}
  1. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换

此时代码等价于:

#include <stdio.h>
int main()
{
	int ret = ((5)*(5)*100);
	printf("%d\n", ret);
	return 0;
}
  1. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

上例不再包含任何由#define定义的符号

我们也要注意以下几点:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
#define FAC(x) (x)*FAC(x-1) //error
  1. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#include <stdio.h>
#define MAX 100
int main()
{
	// 以下代码字符串中的MAX不会被替换为100,而字符串外的MAX会被替换
	printf("MAX = %d\n", MAX); // 结果为MAX = 100
	return 0;
}

五、带有副作用的宏

  代码执行后,除了达到我们想要的结果之外,还导致了其他问题的发生,我们就说该条语句带有副作用

例如,我们现在想比较a和b的大小,并将其较大值赋值给c,之后再将a和b同时加1:

#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
	int a = 10;
	int b = 20;
	int c = MAX(a++, b++);
	printf("%d\n", c);
	return 0;
}

这段代码看似没有问题,但是结果却是不正确的,因为该宏经过替换后,等价于以下代码:

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = ((a++)>(b++)?(a++):(b++));
	printf("%d\n", c);
	
	return 0;
}

所以,当我们使用宏的时候,应该避免传入带有副作用的宏参数

六、宏和函数的对比

  可能通过前面的学习,你也发现了一个问题,就是宏和函数的感觉特别像,那两者可以等同吗?
  事实上是不行的,两者有以下区别:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹(因为函数要开栈帧,宏不用,这个我们下篇会介绍
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的
  3. 宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p2 = MALLOC(10, int);
	if (p2 == NULL)
	{
		printf("p2开辟失败\n");
		return 1;
	}
	
	free(p2);
	p2 = NULL;
	
	return 0;
}

可是,宏也有自己的劣势,在于:

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

倘若用一张图来表明宏和函数的区别,那么可以用下图:
在这里插入图片描述

七、#undef的作用

  #undef可以移除一个#define定义的标识符或宏

#include <stdio.h>
#define MAX 100
int main()
{
	printf("%d\n", MAX);//正常使用
#undef MAX // 此时MAX失效
	printf("%d\n", MAX); //报错,MAX未定义
}

八、# 和

这个我真感觉没什么用,但是还是讲一下吧:

#的作用

把一个宏参数变成对应的字符串

在介绍#的作用的之前,我先向大家说明一下:字符串是有自动连接的特点的:

	char arr[] = "hello ""world!";
	//等价于char arr[] = "hello world!";
	printf("helll ""world!\n");
	//等价于printf("helll world!\n");

认识到这点后,我们来看代码:

#include <stdio.h>
int main()
{
	int age = 10;
	printf("The value of age is %d\n", age);
	double pi = 3.14;
	printf("The value of pi is %f\n", pi);
	int* p = &age;
	printf("The value of p is %p\n", p);
	
	return 0;
}

我们发现,printf要打印的内容大部分是一样的,那么,为了避免代码冗余,我们可不可以将其封装成一个函数或是宏呢?
答案是不行,不信大家可以自行尝试一下,这时候就可以考虑用这个#了:

#include <stdio.h>
#define print(data,format) printf("The value of "#data" is "format"\n",data)
int main()
{
	int age = 10;
	print(age, "%d");
	double pi = 3.14;
	print(pi, "%f");
	int* p = &age;
	print(p, "%p");
	
	return 0;
}

这时我们只需将要打印的变量的变量名和打印格式传入即可。该代码经过预处理后等价于以下代码

#include <stdio.h>
int main()
{
	int age = 10;
	printf("The value of ""age"" is ""%d""\n", age);
	double pi = 3.14;
	printf("The value of ""pi"" is ""%f""\n", pi);
	int* p = &age;
	printf("The value of ""p"" is ""%p""\n", p);
	return 0;
}

又因为字符串有自动连接的特点,所以可以打印出期望的结果

##的作用

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符

#include <stdio.h>
#define CAT(x,y) x##y
int main()
{
	int workhard = 100;
	printf("%d\n", CAT(work, hard));//打印100
	return 0;
}

总结

  其实预处理的过程还是蛮复杂的,关于宏和函数的对比那块大家要自行好好掌握

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

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

相关文章

ax1.twinx()函数介绍

目录 ax1.twinx()实战 ax1.twinx() ax1.twinx() 是 Matplotlib 中用于创建共享 x 轴的双 y 轴的函数。在 Matplotlib 中&#xff0c;当我们希望在同一图中显示不同的数据集&#xff0c;但它们具有相同的 x 轴数据时&#xff0c;可以使用 twinx() 函数创建一个新的坐标轴&#…

(五)、CT球管

第一代和第二代扫描仪使用固定阳极、油冷的X射线管&#xff0c;但由于对输出功率增加的需求&#xff0c;旋转阴极X射线管在CT中变得普遍。 传统固定阳极CT管&#xff0c;当阴极钨丝通过足够的电流时&#xff0c;使其产生白热现象时电子会从钨的表面逸出形成电子云&#xff0c;通…

Lazada菲律宾本土店选品怎么操作?EasyBoss ERP选品功能来帮你!

由于Lazada本土店在流量、履约速度、类目限制以及回款速度方面的优势&#xff0c;越来越多的Lazada卖家都在考虑转型做本土店&#xff0c;但本土化落地并不是一件容易的事&#xff0c;很多卖家在选品阶段就踩大坑了。 因此&#xff0c;为了选品不踩坑&#xff0c;很多卖家都会…

阿里云物联网自有app创建之初始化SDK

文章目录 一、新建工程&#xff0c;配置gradle,导入.so文件&#xff0c;生成apk二、上传apk&#xff0c;集成安全图片&#xff0c;下载SDK三、SDK的集成四、初始化SDK 最近在研究阿里云自有app,这是自己的心得。 一、新建工程&#xff0c;配置gradle,导入.so文件&#xff0c;生…

【论文阅读】超分辨率图像重建算法综述

0. 摘要 研究背景和意义 在人类视觉感知系统中&#xff0c;高分辨率&#xff08;HR&#xff09;图像对于清晰表达空间结构、细节特征、边缘纹理等信息至关重要&#xff0c;在医学、刑侦、卫星等多个领域具有广泛实用价值。超分辨率图像重建&#xff08;SRIR&#xff09;旨在从低…

MQTT vs HTTP:谁更适合物联网?

前言 随着物联网&#xff08;IoT&#xff09;技术的飞速发展中&#xff0c;其应用规模和使用场景正在持续扩大&#xff0c;但它关键的流程仍然是围绕数据传输来进行的&#xff0c;因此设备通信协议选择至关重要。 作为两种主要的通信协议&#xff0c;MQTT 协议和 HTTP 协议各…

AI提示工程:掌握高效Prompt设计的终极指南

导读 提示工程&#xff08;Prompt Engineering&#xff09;是一门新兴的学科&#xff0c;专注于提示词的开发和优化&#xff0c;旨在帮助用户在各种场景和研究领域中更好地利用大语言模型&#xff08;Large Language Model, LLM&#xff09;。掌握相关的提示工程技能将有助于用…

想提升发明专利审查速度有哪些快捷方法?

在创新驱动的时代&#xff0c;发明专利的审查速度对于企业和发明者来说至关重要。了解发明专利的审查程序以及掌握加快审查的快捷途径&#xff0c;能够帮助申请人更快地获得专利授权&#xff0c;保护其创新成果。 一、目前发明专利的审查程序 1. 申请提交&#xff1a;申请人需…

2024年10月上旬更新的6场AI大赛来了,总奖金池:22.9万

亲爱的朋友们&#xff0c;2024年10月的AI大赛最新资讯已上线&#xff01;不要错过这些精彩赛事&#xff0c;快来关注吧&#xff01; 本期我们为您精选了6场AI大赛&#xff0c;主要集中在AI绘画和AI视频创作领域&#xff0c;还有一场别开生面的开放式答题竞赛。这些大赛均由政府…

Java | Leetcode Java题解之第461题汉明距离

题目&#xff1a; 题解&#xff1a; class Solution {public int hammingDistance(int x, int y) {int s x ^ y, ret 0;while (s ! 0) {s & s - 1;ret;}return ret;} }

线性ADRC(LADRC)系统算法框图

非线性ADRC(NLADRC)详细算法框图和源代码请参考专栏系列文章,常用链接如下: 1、NLADRC自抗扰控制 NLADRC自抗扰控制从Simulink仿真到PLC控制实现_自抗扰控制器 simulink仿真-CSDN博客文章浏览阅读1.6k次,点赞2次,收藏7次。本文介绍了如何将ADRC自抗扰控制算法从Simulink…

基于Renesas R7FA8D1BH (Cortex®-M85)自适应蓝牙控制智能小车

目录 概述 1 系统框架结构 2 系统硬件介绍 2.1 电机驱动控制结构 2.1.1 PWM控制小车接口介绍 2.1.2 小车运行方向控制原理 2.2 外围传感器接口 2.2.1 I2C接口设备 2.2.2 IO接口 2.2.3 UART接口 2.2.4 其他接口 2.3 障碍物监测接口 2.4 测速模块接口 3 系统软件架构…

你还在为找不到免费录屏工具而烦恼吗?

嘿&#xff0c;各位小伙伴们&#xff0c;今天咱们来聊聊录屏软件吧&#xff01;你是不是经常需要录制电脑屏幕&#xff0c;做做教程、游戏解说或者工作汇报呢&#xff1f;那你肯定得选个好用的录屏工具啊&#xff01;来来来&#xff0c;跟着我一起&#xff0c;我来给你们推荐几…

SpringBoot项目打成jar包,在其他项目中引用

1、首先新建一个SpringBoot工程 记得要将Gradle换成Maven 2、新建一个要引用的方法 3、打包的时候要注意&#xff1a; ① 不能使用springboot项目自带的打包插件进行打包&#xff0c;下面是自带的&#xff1a; ②要换成传统项目的maven打包&#xff0c;如下图&#xff1a; 依…

算法 动态规划

更多文章&#xff1a;https://www.pandaer.space 动态规划 算法很简单&#xff01;今天我们来聊聊动态规划&#xff0c;我们先从动态规划怎么来的讲起&#xff0c;然后聊聊动态规划应该如何学&#xff1f;最后正式开始动态规划的学习之旅。 动态规划怎么就出现了呢&#xff…

前端性能优化全面指南

前端性能优化是提升用户体验的关键&#xff0c;页面加载速度、响应时间和交互流畅度直接影响用户的留存率和满意度。以下是常用的前端性能优化方法&#xff0c;从网络层、资源加载、JavaScript 执行、渲染性能等方面进行全方位优化。 减少 HTTP 请求 合并文件&#xff1a;将多…

markdown里粘贴图片的同时保存路径 在vscode里实现

下载扩展Markdown Image 设置保存路径 参考链接 https://blog.cxplay.org/works/vscode-to-markdown-editor/#markdown-image

HDLBits中文版,标准参考答案 | 3.2.5 Finite State Machines | 有限状态机(2)

关注 望森FPGA 查看更多FPGA资讯 这是望森的第 17 期分享 作者 | 望森 来源 | 望森FPGA 目录 1 Lemmings 1 2 Lemmings 2 3 Lemmings 3 4 Lemmings 4 5 One-hot FSM | 独热 FSM 6 PS/2 packet parser | PS/2 数据包解析器 7 PS/2 packet parser anddatapath | PS/2 数…

55 WebSocket

55 WebSocket 参考资料 WebSocket SpringBoot使用WebSocket SpringBoot 集成WebSocket详解 前言 WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和…

AI大模型微调产品经理面试必备全攻略,非常详细收藏我这一篇就够了

前言 这两天跟很多做程序员的朋友聊天&#xff0c;怎么看全网火爆的大模型。让我挺意外的是&#xff0c;大家的反馈普遍都很焦虑 。 在AI大模型微调领域的产品经理面试中&#xff0c;总会遇到一系列与技术细节、项目经验、市场趋势以及职业规划相关的问题。以下是一些建议的面…