预编译(1)

目录

预定义符号:

使用:

结果:

预编译前后对比:

#define定义常量:

基本语法:

举例1:

结果:

预编译前后对比:

 举例2:

预编译前后对比:

 注意事项:

#define定义宏:

下⾯是宏的申明⽅式:

举例:

使用:

 预编译前后对比:

 结果:

 注意事项:

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

举例:

正确的:

错误的:

2.不够严谨的写法导致的失误:

举例:

结果:

使用预编译进行查看替换的情况:

所以严谨的写法应该是:

带有副作用的宏:

 举例:

 当我们使用a++和b++这种带有永久性的效果时:

 预编译前后对比:​编辑

结果:

分析:

宏的替换规则:

宏和函数的对比:

优势:

函数和宏的过程:

劣势:

 宏做得到,函数做不到:

什么时候使用宏? 

总结图:



预定义符号:

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

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

使用:

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

结果:

预编译前后对比:

#define定义常量:

基本语法:

#define name stuff

举例1:

int main()
{int a = Mprintf("%d\n",M);printf("%s\n",STR);return 0 :
}
结果:

 

预编译前后对比:

 

 举例2:

#define forever for( ; ; )
int main()
{int a = M;printf("%d\n",M);printf("%s\n",STR);forever;return 0;
}

结果是无限循环

预编译前后对比:

 

 注意事项:

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

#define定义宏:

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

下⾯是宏的申明⽅式:

 #define name( parament-list ) stuff

举例:

#define SOAURE(X) X*X

使用:

#define SOAURE(X) X*X
int main()
{int a = 5;printf("%d\n",SQUARE(a));return 0;
}
 预编译前后对比:

 结果:

 注意事项:

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

举例:
正确的:
#define SQUARE(X) X*X
错误的:
#define SQUARE (X) X*X

错误在于SQUARE举例(x)之间有一个空格,正常来说是不因该又间隔的,这二者应该紧贴在一起,也就是换在原型上来说,name应该和 ( parament-list )紧贴着!

2.不够严谨的写法导致的失误:

举例:
#define SOAURE(X) X*X
int main()
{int a = 5;printf("%d\n",SQUARE(a+2));return 0;
}

按照我们的想法因,再进行#define替换后,应该是(a+2)*(a+2),因为a=5,所以最后的结果因该是7*7=49,但是结果并不是如此。

结果:

使用预编译进行查看替换的情况:

并不是我们想象中的 (a+2)*(a+2),而是a+2*a+2,也就是5+2*5+2=5+10+2=17

所以严谨的写法应该是:
#define SQUARE (X) ((X)*(X))

 以此避免因为不严谨而带来的错误。

带有副作用的宏:

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

副作用就是表达式求值的时候出现的永久性效果。 

 举例:

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

 当我们使用a++和b++这种带有永久性的效果时:

int main()
{int a = 15;int b = 9;int m = MAX(a++, b++);printf("%d\n",m);printf("a=%d b=%d\n",a,,b);return 0;
}

 预编译前后对比:

结果:

分析:

通过宏的替换,将   int m = MAX(a++, b++);替换为了int m = ((a++)>(b++)?(a++):(b++));

也正是因为替换,导致了++的不确定性。

  1. 1.因为a=15,再(a++)>(b++)中进行了使用,使用完后 a和b都因为++加上了1
  2. 2.因为再(a++)>(b++)中a比b大,所以执行?(a++):(b++));因为是a大,所以只执行(a++),所以最后答案输出给m的是16,但是输出完后,进行++执行,所以a又继续+1,得到17

宏的替换规则:

通过上诉我们得知了,宏是先把对应的内容替换掉,再进行运算。

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

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

注意事项:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。 

如图所示:printf("M = %d\n",M);中的M =%d\n的M并未被替换 。

 

宏和函数的对比:

 

如上述代码所示:宏和函数,那一个更好呢?

其实,在函数和宏中,其实对于较为简单的代码来说,宏是一种更好的选择,也是宏的一种优势。

优势:

  • 函数是进行调用的,而宏是直接进行替换的,对于调用再编译器中是有一个过程的,而对于替换而言,这种替换的过程比调用要简单的多 
#define MAX(a,b) ((a)>(b)?(a):(b))
int m = MAX(100,101);
int m = ((100) > (101) ? (100) :(101));

再预编译中,上诉的两个代码其实是一个东西, 因为宏的作用下,编译器直接将第一行代码变成了第二行代码,或者说,直接把第一行代码看出第二行代码对待,这种方式是宏特有的替代。

也正是因为这种直接替代,使得宏在简单的代码中比调用函数的运行速度更快。

而调用函数:

 在使用调用函数正式调用之前,会执行一堆代码指令,当这一堆指令完成后才开始进行调用函数。

这也是宏比函数快的原因。

函数和宏的过程:

  • 宏因为是替换,所以宏的参数是没有类型的。 

这是比函数快的原因,也是一个不确定的因素。

劣势:

  • 因为每一次使用宏,就会有一段代码被宏替代,若设置的宏较长,那么整体的代码会被宏拉长,反倒是函数因为可以多次使用会更简洁。
  •  关于调试,当程序运行的时候,预处理就以及结束了,看到的宏的代码已经被预编译了,看不到预编译之前的宏,所以也可以说,宏不能逐步调试。
  • 其次最后,因为参数没有类型,所以不是非常稳定。
  • 以及,优先级问题,也就是之前设置的宏不够严谨导致参数的运算过程中符号优先级的出错。
#define SOAURE(X) X*X//不够严谨的写法

 宏做得到,函数做不到:

当我们开辟空间的时候,如果怕麻烦的花可以使用宏来定义。

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

使用宏后:

#define MALLOC(n,type) (typex)malloc(n*sizeof*(type))

利用了宏是可以把类型当参数进行传值替代的特点,这就是函数做不到的地方。 

#define MALLOC(n,type) (typex)malloc(n*sizeof*(type))
int main()
{int* p = MALLOC(10,int);return 0;
}

什么时候使用宏? 

  • 计算逻辑如果比较简单,就可以使用宏
  • 计算逻辑比较复杂,建议使用函数 

总结图:

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

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

相关文章

ELK介绍

一、前言 前面的章节我们介绍通过ES Client将数据同步到ElasticSearch中,但是像日志这种数据没有必要自己写代码同步到ES那样会折腾死,直接采用ELK方案就好,ELK是Elasticsearch、Logstash、Kibana三款开源软件的缩写,ELK主要用于…

P2PNet-Soy原理梳理

前文总结了P2PNet源码以及P2PNet-Soy源码实现方法,相关链接如下: 人群计数P2PNet论文:[2107.12746] Rethinking Counting and Localization in Crowds:A Purely Point-Based Framework (arxiv.org) p2p人群计数源码:GitHub - Te…

云服务器租用价格表概览_阿里云腾讯云华为云

云服务器租用价格多少钱一年?阿腾云分享阿里云、腾讯云和华为云的云服务器租用价格表:阿里云2核2G服务器108元一年起、腾讯云2核2G3M带宽轻量服务器95元一年、华为云2核2G3M云耀L实例89元一年起,阿腾云分享更多关于云服务器租用价格明细&…

Kubernetes基础(五)-Service

1 引言 Service 主要用于提供网络服务,通过Servicel的定义,能够 为客户端应用提供稳定的访问地址(域名或IP地址)和负载均衡功能,以及屏蔽后端Endpoint的变化,是Kubernetes实现微服务的核心资源。 本文详细…

博弈论中静态博弈经典场景案例

博弈论中静态博弈经典场景案例 1、齐威王田忌赛马 田忌赛马是中国家喻户晓的故事,故事讲述的是齐国大将田忌的谋士孙膑如何运用计谋帮助田忌在与齐威王赛马时以弱胜强的故事,这个故事其实本质也是一个博弈的过程。     齐威王要和田忌赛马&#xff…

二叉树MFC实现

设有一颗二叉树如下; 这似乎是一颗经常用作示例的二叉树; 对树进行遍历的结果是, 先序为:3、2、2、3、8、6、5、4, 中序为:2、2、3、3、4、5、6、8, 后序为2、3、2、4、5、6、8、3&#xff1b…

MySQL学习笔记25

逻辑备份 物理备份 在线热备: 真实案例: 数据库架构是一主两从,但是两台从数据库和主数据不同步。但是每天会全库备份主服务器上的数据到从服务器上。需要解决主从不同步的问题。 案例背后的核心技术: 1、熟悉MySQL数据库常见…

【计算机视觉|人脸建模】PanoHead:360度几何感知的3D全头合成

本系列博文为深度学习/计算机视觉论文笔记,转载请注明出处 标题:PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ 链接:[2303.13071] PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ (arx…

大数据Doris(三):Doris编译部署篇

文章目录 Doris编译部署篇 一、Doris编译

学信息系统项目管理师第4版系列13_立项管理

1. 项目立项管理包括 1.1. 项目建议与立项申请 1.2. 项目可行性研究 1.2.1. 初步可行性研究 1.2.2. 详细可行性研究 1.2.2.1. 不可缺少 1.2.2.1.1. 【高21上选21】 1.2.3. 可以依据项目的规模和繁简程度合二为一 1.3. 项目评估与决策 2. 立项申请 2.1. 项目建议书 2…

Lua语法之简单变量

--nil有点类似空null a nil print(a) --type函数得到类型 返回值是string print(type(a)) print("*****")--number是数值 int float这些 --lua的变量可以随便赋值 自动识别类型 a 1 print(a) print(type(a)) print("*****")--siting可以用单引号双引号 a…

华为云HECS云服务器docker环境下安装nginx

前提:有一台华为云服务器。 华为云HECS云服务器,安装docker环境,查看如下文章。 华为云HECS安装docker-CSDN博客 一、拉取镜像 下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx查看镜像 dock…

实体行业数字化转型怎么做?线上线下相结合的新零售体系怎么做?

如今,实体行业想要取得收入增长,只做线下业务或者只做线上业务,在当前的市场环境中是难以长久生存的,因此一定要线上线下相结合,将流量运作与线下转化进行充分结合,才能更好地发挥实体优势,带来…

Linux学习记录——삼십일 socket编程---TCP套接字

文章目录 TCP套接字简单通信1、服务端1、基本框架2、获取连接 2、客户端3、多进程4、多线程5、线程池6、简单的日志系统7、守护进程8、其它 TCP套接字简单通信 本篇gitee 学习完udp套接字通信后,再来看TCP套接字。 四个文件tcp_server.hpp, tcp_serve…

什么是Local Storage和Session Storage?它们之间有什么区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是 Local Storage 和 Session Storage?Local Storage(本地存储)Session Storage(会话存储) ⭐ 区别⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的…

程序员的浪漫:如何用java代码画❤️表白呢?

有位小伙伴说,看到一个帖子,一个计算机博士接亲时,要求现场写代码,5分钟做出一个爱心。我们就看看如何用java设计出心形的代码。 我找了一下,发现方法竟然很多,我们就来见识一下,最后我们看一下…

TouchGFX之字体缓存

使用二进制字体需要将整个字体加载到存储器。 在某些情况下,如果字体很大,如大字号中文字体,则这样做可能不可取。 字体缓存使应用能够从外部存储器只能加载显示字符串所需的字母。 这意味着整个字体无需保存到在可寻址闪存或RAM上&#xff…

Java自学(三)面向对象编程

目录 什么是面向对象 举例 this关键字和构造器 实体类 电影小案例 什么是面向对象 我们日常生活中谈到一个事物,总会描述它的性质与行为,这个事物也就是 ”对象”。比如一个学生对象,他的属性有姓名、学号、成绩......他的行为有上课、…

装饰器模式详解和实现(设计模式 二)

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地将对象添加到现有对象中,以提供额外的功能,同时又不影响其他对象。 实现示例 1.定义一个接口或抽象类,表示被装饰对象的公共接口 //抽…

关于 自定义的RabbitMQ的RabbitMessageContainer注解-实现原理

概述 RabbitMessageContainer注解 的主要作用就是 替换掉Configuration配置类中的各种Bean配置; 采用注解的方式可以让我们 固化配置,降低代码编写复杂度、减少配置错误情况的发生,提升编码调试的效率、提高业务的可用性。 为什么说“降低…