详解预处理(1)

目录

预定义符号

预处理指令#define

#define定义符号

#define定义宏

#define替换规则

#和##(C语言预处理操作符)

#

##

带副作用的宏参数

宏和函数的对比

命名约定


在之前我们学习了一个文本文件.c生成一个可执行程序。今天我们详细讲解其中的编译下的预处理这个步骤的我们需要掌握的知识点。

预定义符号

标准C语言预定义的符号,在预处理阶段可以直接使用。 这些预定义符号都是语言内置的。

  • __FILE__    进行编译的源文件
  • __LINE__    文件当前的行号
  • __DATE__   文件被编译的日期
  • __TIME__    文件被编译的时间
  • __STDC__   如果编译器遵循ANSI C,其值为1,否则未定义
  • __FUNCTION__ 文件当前的函数名字
#include<stdio.h>
int main()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);//printf("%s\n", __STDC__);//当前使用的是VS2022不遵循ANSI Cprintf("%s\n", __FUNCTION__);return 0;
}

  • 换到gcc的底下就可以使用。printf("%s\n", __STDC__); 

预处理指令#define

接着我们来介绍#define。#define是一种预处理指令

#define定义符号

#define定义常量就是定义标识符。

语法:#define  name  stuff

  • name是名字
  • stuff是内容
  • #define MAX 100
  • #define M 3+5
  • #define 仅仅只是替换符号,不进行计算。
  • #defien在定义标识符的时候,不要在最后加上
#define MAX 100
#define STR "abcdef"
#define INT int
#define M 3+5
int main()
{int a = MAX;INT b = 0;printf("%s", STR);int c = M;return 0;
}

除了上面示例以外还有特殊的写法。

#define MAX 100
#define STR "abcdef"
#define INT int
#define forever for(;;)
int main()
{int a = MAX;INT b = 0;printf("%s", STR);forever;//替换之后for (;;);//死循环return 0;
}
#define CASE break;case
int main()
{int n = 0;scanf("%d", &n);switch (n){case 1:CASE 2:CASE 3:}return 0;
}
//替换之后
#define CASE break;case
int main()
{int n = 0;scanf("%d", &n);switch (n){case 1:break; case 2:break; case 3:}return 0;
}
#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 MAX 1000;
#include<stdio.h>
int main()
{int a = MAX;return 0;
}#define MAX 1000;
#include<stdio.h>
int main()
{int a = 1000;;//空语句return 0;
}

像上面这种情况可能没有问题,但是建议不要加上 ; ,这样容易导致问题。 比如下面的场景,会出现语法错误:

#define MAX 1000;
#include<stdio.h>
int main()
{int a = 10;int b = 0;if (a>5)b = MAX;elsemax = 0;return 0;
}#define MAX 1000;
#include<stdio.h>
int main()
{int a = 10;int b = 0;if (a > 5)b = 1000;;//空语句这里就有问题了elsemax = 0;return 0;
}
#define MAX 1000;
#include<stdio.h>
int main()
{printf("%d", MAX);return 0;
}#define MAX 1000;
#include<stdio.h>
int main()
{printf("%d", 1000;);//语法错误return 0;
}

#define定义宏

宏是什么?

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

语法

 #define  name( parament-list )  stuff

  • name 是名字
  • parament-list 是一个由逗号隔开的符号表,可能出现在stuff中,可以没有/一个/两个均可。
  • parament-list 的参数可能会出现在  stuff 文本内容中。
  • 参数列表的左括号必须与name紧邻。
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
  • 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
#define ADD(x,y) x+y                                                
#include<stdio.h>
int main()
{int a = 10;int b = 20;int c = ADD(a, b);printf("%d\n", c);return 0;
}

看了上面#define定义宏的使用,相信你很快就上手了。


但是这个简单的宏却存在重要错误隐患。

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

我们原本想计算4(a+b)的值,应该是90,但是结果确是60。因为替换之后的代码:

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

 有人说将x+y加上括号(x+y)即可,但是这样真的就安全了吗?

#define ADD(x,y) (x*y)                                                
#include<stdio.h>
int main()
{int a = 10;int b = 20;int c = 4*ADD(a+1, b+1);
//  int c=4*(11*21)=4*(32)printf("%d\n", c);return 0;
}

 我们原本想计算的是4((a+1)*(b+1))结果,但是没有达到我们想要的结果。为什么呢?

#define ADD(x,y) (x*y)                                                
#include<stdio.h>
int main()
{int a = 10;int b = 20;int c = 4*(a+1*b+1);
//int c=4*(10+20+1)=4*(31)printf("%d\n", c);return 0;
}

综上所诉:宏在书写的时候尽量带上足够多的有效的括号

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。导致计算的顺序发生变化。 

#define替换规则

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

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

特别提醒:

  • 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
  • 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#define M 100
#define ADD(x,y)  ((x)*(y))//这里的文本内容是不能出现宏的:类似函数递归的这种情况的
#include<stdio.h>
int main()
{int a = 10;int b = 20;int c = 4 * ADD(M, b+10);//M首先会被替换printf("%d\n", c);printf("Master\n");//这里的M是不会被替换return 0;return 0;
}

#和##(C语言预处理操作符)

#

提问:如何把参数插入到字符串中?这里我们引入: 

# 就是把宏的参数插入到字符串中


首先,我们需要知道:字符串是有自动连接的特点的。

合并打印和分开打印都能达成一样的效果。

#include<stdio.h>
int main()
{printf("hello word\n");printf("hello"" word\n");return 0;
}

 


 看下面这个代码,我们发现有非常多重复的地方,我们能简化代码,把重复的地方封装成函数。

#include<stdio.h>
int  main()
{int a = 10;printf("the value of a is %d\n", a);int b = 20;printf("the value of b is %d\n", b);float f = 3.14f;printf("the value of f is %f\n", f);return 0;
}

我们发现并不能封装成一个函数,因为类型不同,打印的符号也不同。那能不能写一个宏呢? 

宏当然可以解决,宏没有类型的限制。宏只负责替换,不管类型的。

(#n  == "n"  替换到文本内容里面去,把一个宏参数变成对应的字符串)

                           //printf("the value of " "n" " is " "%d" "\n", n);
#define PRINT(n,format) 	printf("the value of "#n" is "format"\n", n);
#include<stdio.h>
int  main()
{int a = 10;PRINT(a, "%d");//写一个宏,把需要打印的变量和类型传过去int b = 20;PRINT(b, "%d");float f = 3.14f;PRINT(f, "%f");return 0;
}
//在宏参数面前加# 不是让宏参数完整的替换,而是以字符串的形式替换过去

 

##

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

: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。 

#define CAT(v,n)  v##n
#include<stdio.h>
int main()
{int value10 = 100;printf("%d\n", CAT(value, 10));return 0;
}

 

带副作用的宏参数

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

#include<stdio.h>
#define MAX(x,y)  ((x)>(y)?(x):(y))
int main()
{int a = 3;int b = 5;int m = MAX(a++, b++);printf("%d\n", m);//6printf("%d\n", a);//4printf("%d\n", b);//6return 0;
}

 为什么和我们预期的不一样??因为宏的参数是直接替换进去的,不做任何其他处理。

#include<stdio.h>
#define MAX(x,y)  ((x)>(y)?(x):(y))
int main()
{int a = 3;int b = 5;int m = MAX(a++, b++);//int m=((a++)>(b++)?(a++):(b++));//        3  >  5//        4     6//                 no       6//                          7printf("%d\n", m);//6printf("%d\n", a);//4printf("%d\n", b);//7return 0;
}

 

宏和函数的对比

宏:通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

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

宏的优势:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
  • 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关
  • 宏有时候可以做函数做不到的事情。
  1. 宏的参数可以出现类型,但是函数做不到
  2. 宏的操作符# 和 ##  ,在函数里面是没有的
#define MALLOC(num,type)   (type*)malloc(num*sizeof(type))
int main()
{int* p = MALLOC(10, int);////>....return 0;
}

宏的劣势

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  • 宏是没法调试的。
  • 宏由于类型无关,也就不够严谨。
  • 宏可能会带来运算符优先级的问题,导致程容易出现错。
属性#define函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中多个位置,所以带有副作用参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
调试宏是不方便调试的函数是逐语句逐过程调试的
递归宏是不能递归的函数是可以递归的

建议:如果逻辑比较简单,可以使用宏来实现。但是如果计算逻辑比较复杂,就使用函数。

C99之后,C++引入了内联函数的概念,后期我们学习了C++,inline内联函数具有函数和宏的双重特点,内联函数是函数,却又像宏一样,在调用的地方展开。

命名约定

一般来讲函数的宏的使用语法很相似。

所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:

把宏名全部大写 函数名不要全部大写

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com】

联系------→【邮箱:2784139418@qq.com】 

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

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

相关文章

01 # 手写 new 的原理

new 做了什么? 在构造器内部创建一个新的对象这个对象内部的隐式原型指向该构造函数的显式原型让构造器中的 this 指向这个对象执行构造器中的代码如果构造器中没有返回对象&#xff0c;则返回上面的创建出来的对象 手写 new 的过程 new 是一个运算符&#xff0c;只能通过函…

CSS隐藏元素的N种方法,你知道哪一种最适合你?

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、前…

Camtasia2024永久激活码

真的要被录屏软件给搞疯了&#xff0c;本来公司说要给新人做个培训视频&#xff0c;想着把视频录屏一下&#xff0c;然后简单的剪辑一下就可以了。可谁知道录屏软件坑这么多&#xff0c;弄来弄去头都秃了&#xff0c;不过在头秃了几天之后&#xff0c;终于让我发现了一个值得“…

【Linux】gdb调试

目录 进入调试查看代码运行代码断点打断点查断点删断点从一个断点转跳至下一个断点保留断点但不会运行该断点 退出调试逐过程逐语句监视跳转至指定行运行结束当前函数 进入调试 指令&#xff1a;gdb 【可执行文件】&#xff1a; 查看代码 &#xff1a;l 【第几行】如果输入指…

安全设备

一.防火墙 5层应用层 防火墙 4层 udp tcp 协议 华为 厂商 华为 h3 1.区域划分 Dmz 停火区 Untrust 不安全区域 Trust 安全区域 防火墙 默认禁止所有 二.Waf Web 应用防火墙 放到web前面 产品 雷池 绿盟 软件 安…

递归神经网络 (RNN)

弗朗西斯科佛朗哥 一、说明 循环神经网络非常有趣&#xff0c;因为与前馈网络不同&#xff0c;在前馈网络中&#xff0c;数据只能在一个方向上传播&#xff0c;每个神经元可以与连续层的一个或多个神经元连接&#xff0c;在这种类型的网络中&#xff0c;神经元还可以环回自身或…

安卓端GB28181设备接入模块如何实现实时位置订阅(MobilePosition)

技术背景 实时位置&#xff08;MobilePosition&#xff09;订阅和上报&#xff0c;对GB28281设备接入终端尤其重要&#xff0c;如移动单兵设备、执法记录仪、智能安全帽、车载终端等&#xff0c;Android国标接入设备通过获取到实时经纬度信息&#xff0c;按照一定的间隔上报到…

竞赛选题 深度学习卫星遥感图像检测与识别 -opencv python 目标检测

文章目录 0 前言1 课题背景2 实现效果3 Yolov5算法4 数据处理和训练5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **深度学习卫星遥感图像检测与识别 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐…

如何使用 VuePress 搭建博客网站并 Vercel 部署

先来看一下网站截图&#xff1a; 快速上手 1.创建并进入一个新目录 mkdir vuepress-starter && cd vuepress-starter2.使用你喜欢的包管理器进行初始化 yarn init # npm init3.将 VuePress 安装为本地依赖 yarn add -D vuepress # npm install -D vuepress4.创建你…

【LeetCode:2520. 统计能整除数字的位数 | 模拟 | HashMap】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Sentinel授权规则和规则持久化

大家好我是苏麟 , 今天说说Sentinel规则持久化. 授权规则 授权规则可以对请求方来源做判断和控制。 授权规则 基本规则 授权规则可以对调用方的来源做控制&#xff0c;有白名单和黑名单两种方式。 白名单&#xff1a;来源&#xff08;origin&#xff09;在白名单内的调用…

面试必考精华版Leetcode994. 腐烂的橘子

题目&#xff1a; 代码&#xff08;2023年10月26日 首刷自解&#xff09;&#xff1a; class Solution { public:int orangesRotting(vector<vector<int>>& grid) {/*统计grid二维数组中有多少新鲜橘子和多少烂橘子&#xff0c;将所有的烂橘子存入一个三元队列…

C++类模板再学习

之前已经学习了C类模板&#xff1b;类模板的写法和一般类的写法有很大的差别&#xff1b;不容易熟悉&#xff1b;下面再做一遍&#xff1b; 做一个椭圆类&#xff0c;成员有长轴长度和短轴长度&#xff1b; // ellipse.h: interface for the ellipse class. // //#if !define…

图论基础和表示

一、概念及其介绍 图论(Graph Theory)是离散数学的一个分支&#xff0c;是一门研究图(Graph)的学问。 图是用来对对象之间的成对关系建模的数学结构&#xff0c;由"节点"或"顶点"(Vertex&#xff09;以及连接这些顶点的"边"&#xff08;Edge&a…

2023年中国医疗器械供应链服务平台发展趋势分析:向国家高端化市场发力[图]

医疗器械供应链服务主要分为全流程供应链服务与院内SPD服务&#xff0c;同时全流程供应链服务主要分为市场、仓储物流与金融三大服务。在SPD数字化赋能下&#xff0c;大数据、云计算等技术支撑促进一站式数字化供应链业务协同平台&#xff0c;带动了整体医疗器械供应链服务的发…

730. 机器人跳跃问题--二分

题目&#xff1a; 730. 机器人跳跃问题 - AcWing题库 思路&#xff1a; 二分 1.当起始能量E大于最大建筑高度1e5 时&#xff0c;E的能量在整个条约过程中全程递增&#xff0c;则大于E的初始能量也必然成立&#xff08;满足二段性&#xff09;。故最小初始能量范围为[0,1e5]&a…

windows如何查看电脑IP地址

介绍两种查看电脑IP的方式 一、第一种方式 1、在电脑左下角搜索网络连接 2、鼠标右键你目前连接的网络&#xff08;wifi就点wifi 网线就点以太网&#xff09;&#xff1b;选择里面的状态。 3、点击详细信息&#xff0c;这里的IPv4地址就是你电脑的IP。 二、第二种 1、win…

ElasticSearch安装、插件介绍及Kibana的安装与使用详解

ElasticSearch安装、插件介绍及Kibana的安装与使用详解 1.安装 ElasticSearch 1.1 安装 JDK 环境 因为 ElasticSearch 是用 Java 语言编写的&#xff0c;所以必须安装 JDK 的环境&#xff0c;并且是 JDK 1.8 以上&#xff0c;具体操作步骤自行百度 安装完成查看 java 版本 …

go创建完美的枚举类型

文章目录 一.前言二. 枚举基本要素描述三. 枚举设计源码3.1 EnumCommon-通用能力3.2 Enum_news 业务枚举3.3 定制化业务枚举 一.前言 用惯了springboot和Jakarta.才发现springboot和Jakarta的语言是多么精妙! 一些场景我们需要使用枚举: 如建立字典值映射,仅通过代码实现方便快…

竞赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…