C语言·预处理详解

1. 预定义符号

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

                __FILE__  进行编译的源文件

                __LINE__  文件当前的行号

                __DATE__  文件被编译的日期

                __TIME__  文件被编译的时间

                __STDC__  如果编译器遵循ANSI C,其值为1,否则未定义

        举个例子

        这行代码的出现位置是在第16行,所以__LINE__的值就是16,注意这个词是两个下划线+LINE+两个下划线,文件名,行号,日期,时间都是编译当前文件当前位置当前时间的信息,注意是编译时的不是运行时的信息。

2. #define定义常量

        基本语法:

                #define name stuff

        #define定义的stuff内容可以是数值,参数,甚至是语句,举个例子:

        最后一段定义中我们定义了一个打印的语句,很明显我是将一行代码写成了3行。这是因为写成一行代码的时候代码量太长,因此我们可以借助续行符进行换行接着写。前两行结尾处的反斜杠就是续行符,我们可以理解成将反斜杠后面的回车转义掉了,所以回车换行相当于没换,视觉上像是换了行,但实际上还是在这一行上书写

        现在我们思考一个问题:在使用#define定义标识符的时候要不要在最后加上  ;  

        我建议是不要加上的,因为#define是暴力替换掉常量名,如果不注意的话很容易出问题

        观察num被替换之后的样子我们可以发现加  ;  的危害了

3. #define定义宏

        于定义常量类似,定义宏也是将名暴力替换掉,只不过宏新增了一个参数的机制

        下面是宏的声明方式

                #define name(parament-list) stuff

        parament-list参数列表是一个由符号隔开的符号表,其中的符号可能出现在stuff中

        注意:参数列表的左括号必须和name紧邻,如果两者间由任何空白的存在,参数列表就会被认为是stuff的一部分

        举例如何使用定义宏

                        

        我用宏实现了一个将参数变成其二倍的功能

        但是这么写宏是有问题的

                        ​​​​​​​

        我在宏的参数部分写2+1,本意是计算DOUBLE(3),但是因为暴力替换的问题,表达式并没有按照我预想的循序计算,而计算的是2*2+1

        因此我们在定义宏的时候要将表达式中的元素用小括号括起来,保证它们的运算顺序不会在暴力替换之后改变

        但是问题还没有解决

                        ​​​​​​​

        很明显,这段代码也因为暴力替换的问题本想计算5*4,但是实际上计算的是5*2+2

        因此我们要将整体也用小括号固定计算顺序

                           ​​​​​​​

        这样才算正确的定义宏

4. 带有副作用的宏参数

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

                x+1 不带有副作用

                x++ 带有副作用

        举一个副作用的例子

        

       很明显结果和我们预期的不一样,判断3和5的大小最后答案竟然是6,而且a和b的值也被改变,这正是因为参数的副作用带来的问题,简单分析一下注释掉的那条语句就可以知道到底是怎么回事了

5. 宏的替换

        我们之前已经提到过多次了,宏在使用时其实就是将名强行替换掉了,那么具体的替换方案涉及以下几个步骤

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

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

        3.最后,再对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,重复以上处理步骤

        注意:

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

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

        

6. 宏和函数的对比

        宏通常被应用于执行简单的运算,函数用于解决复杂的功能

        比如在执行上面那个比较两个数大小的功能的时候,写成宏更有优势

        宏的优势:

        1. 进行简单计算的时候需要的计算量比函数更小,宏没有像函数的调用和返回那样复杂的汇编语句需要,因此速度也会比函数快

        2. 宏是与类型无关的,所以参数想传什么就传什么,具体产生什么效果等替换完了再看。甚至宏可以传递类型名,这是函数绝对做不到的

        宏的劣势:

        1. 每次使用宏的时候,一份宏定义的代码将插到程序当中去。除非宏比较短,否则可能大幅增加宏的长度

        2. 宏是没法调试的

        3. 宏由于类型无关,因此不够严谨

        4. 宏可能会带来运算符优先级的问题,导致容易出错
 

7. #和##

7.1 #运算符

        #运算符将宏的一个参数转换成字符串字面量。它仅允许出现在带参数的宏的替换宏的替换列表中

        #执行的操作可以理解为字符串化

        举个例子:

        这段代码中#n在替换时发生字符串化,将 a 变成了 "a"

        当然在观察注释的时候可以注意到printf中包含了3段字符串,这种写法是允许的,它和第二行注释是等价的

        

7.2 ##运算符

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

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

        下面我们举个例子:

        当我们想比较两个数的大小的时候不同的类型int、float、double都要写一个不同名函数来运算,但其实它们的运算内容方法是一样的,所以能不能有一种方法把它们整合一下

        ##在这段代码中就起到了粘贴 type 和 _max 的作用,使得type会被替换之后还能与_max形成新的函数名,用这种方法通过一段代码生成了3个功能相似,函数名不同的函数

8. 命名约定

        一般来讲,函数和宏的使用语法很相似,所以为了区分二者,我们有这样的书写习惯

                把宏全部大写

                函数名不要全部大写

9. #undef

        这条指令用于移除一个宏定义

        举个例子

        ​​​​​​​        

        在宏定义移除之后MAX就不能用了,但是我们可以用#define重新启用他,起到一个修改宏的作用

10. 命令行定义

        许多C的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译的过程

        举个例子,如果我在编写代码的时候不能确定一个数组要分配多大的空间,只能在运行前的时候确定,可以这样先把程序写出来

        ​​​​​​​        ​​​​​​​        

        这样在程序中并不指定SIZE的具体大小,在启动程序的时候用命令行

                gcc -D SIZE=10 programme.c

        这样程序就能正常跑起来,并且开辟一个10个元素的数组

        这个功能vs中不好演示,感兴趣可以用Linux尝试一下

11. 条件编译

        在编译一个程序的时候我们可以使用条件编译指令来控制要不要编译这条语句

        比如说那些调试性的代码,删除可惜,保留又碍事,所以我们可以选择性编译

        常见的条件编译指令

        1.#if #endif

                #if 常量表达式(为真编译,为假不编译)

                ······

                #endif

        注意#if后面跟的是常量表达式

                

        这么写就是有错误的。第一,#if 后边要求跟常量表达式,a是变量。第二,#if 是在预处理阶段进行处理的,这时候a还没有定义呢,那a算什么?

        2. 多分支的条件编译

                #if 常量表达式

                ······

                #elif 常量表达式

                ······

                #else

                ······

                #endif

        3. 判断是否被定义

                #if defined(symbol)

                #ifdef symbol

        这两条语句都是说如果这个symbol被定义了就编译下面的代码

                #if !defined(symbol)

                #ifndef symbol

        这两条语句是说如果没定义symbol就编译下面的代码

        当然不要忘记 #endif 结束条件编译,#endif和距它最近的 #if 或 #if··· 匹配,不管是 #if 还是 #if··· 都要记得用 #endif 结束

        4.条件编译指令是可以嵌套的

12. 头文件的包含

12.1 头文件被包含的方式

12.1.1 本地文件包含

                #include "filename.h"

        查找策略:现在源文件目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果还是找不到就报错

12.1.2 库文件包含

                #include <filename.h>

        查找这种类型的头文件就直接去标准路径下查找(所以库函数所在位置),如果找不到就报错

        这样是不是可以说,对于库函数包含的时候可不可以用  " "  的形式?

        确实是可以的,但是这样做效率就会低一点,同时这样也不容易区分我包含的是库文件还是本地文件

12.2 嵌套文件包含

        我们之前已经学到过#include就是把另一个文件复制到这条语句所在位置,并替换掉这条语句。

        那么在一个大型的项目中,在所难免的会出现头文件被重复包含的现象,这会导致多次把头文件中的内容粘过来,如果头文件过大,这很影响编译的速度

        所以我们可以用条件编译解决这个问题,在头文件开头写:

        ​​​​​​​        ​​​​​​​        

        这样就可以通过判断__TEST_H__是否被定义来确定这个头文件有没有使用过,从而确定要不要把头文件的内容粘过去

        还有一种方法,在头文件开头加上:

        ​​​​​​​        ​​​​​​​        

        就可以避免头文件的重复引用,这个方法是比较常见的

13. 其他预处理指令

                #error

                #pragma

                #line

                #pragma pack()    修改默认对齐数,在结构体中有讲

                ······

        更多内容可以参考《C语言深度解剖》中预处理一章

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

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

相关文章

SpringCloud Aliba-Sentinel【下篇】-从入门到学废【6】

&#x1f929;java小课堂&#x1f929; &#x1f32d;&#x1f32d;&#x1f32d; 和 equals 的区别是什么&#xff1f;&#x1f979;&#x1f979;&#x1f979; 对于基本类型&#xff0c; 比较的是值&#xff1b;对于引用类型&#xff0c;比较的是地址&#xff1b;equals不能…

【现代密码学基础】详解完美安全与不可区分安全

目录 一. 介绍 二. 不可区分性试验 三. 不可区分性与完美安全 四. 例题 五. 小结 一. 介绍 敌手完美不可区分&#xff0c;英文写做perfect adversarial indistinguishability&#xff0c;其中adversarial经常被省略不写&#xff0c;在密码学的论文中经常被简称为IND安全。…

ICLR 2024 时间序列相关最新论文汇总,涉及transformer、GNN、大模型等热门领域

ICLR&#xff08;International Conference on Learning Representations&#xff09;&#xff0c;国际公认的深度学习顶会之一&#xff0c;与AAAI、CVPR、ACL和NIPS等老牌学术会议齐名&#xff0c;由图灵奖巨头Yoshua Bengio和Yann LeCun牵头举办&#xff0c;在人工智能、统计…

Spring | Srping AOP (AOP简介、动态代理、基于“代理类”的AOP实现)

目录: 1.Spring AOP简介1.1 AOP简介1.2 AOP术语 2.动态代理2.1 JDK动态代理2.2 CGLIB代理 3.基于“代理类”的AOP实现3.1 Spring的通知类型3.2 ProxyFactoryBean ( 可通知.xml配置文件完成aop功能 ) 1.Spring AOP简介 1.1 AOP简介 Spring的AOP模块&#xff0c;是Spring框架体系…

SpringMVC获取参数与页面跳转

获取参数 第一种 直接当成方法的参数&#xff0c;需要与前台的name一致 相当于Request.getAttribute("username") Controller 第二种 使用对象接收 页面的name也要和对象的字段一致 创建一个对应的实体类 Controller 将参数更换为User对象就行 SpringMVC获取到…

P2P DMA并不是所有场景都会有性能提升

P2P (Peer-to-Peer) DMA技术理论上可以带来性能提升&#xff0c;特别是在特定的工作负载和场景下。例如&#xff0c;当两个高速设备&#xff08;如GPU与NVMe SSD&#xff09;需要频繁进行大量数据交换时&#xff0c;通过P2P DMA&#xff0c;数据可以直接在设备间传输&#xff0…

结构体内存对齐(面试重点)

结构体内存对齐 1. 结构体类型的声明1.1 结构体的概念1.1.1 结构的声明1.1.2 结构体变量的创建和初始化 1.2 结构的特殊声明1.3 结构的自引用 2. 结构体内存对齐2.1 对齐规则2.1.1 练习1:2.1.2 练习2:2.1.3 练习3:2.1.4 练习4: 2.2 offsetof宏的使用2.3 为什么存在内存对齐?2.…

electron + selenium报错: Server terminated early with status 1

解决办法&#xff1a; 这种错误一般是浏览器创建的某方法致命错误导致的&#xff0c;查看一下实例化driver的地方有哪些配置&#xff0c;着重看日志、用执行信息存储一类的配置&#xff0c;我的问题是日志文件夹改过了但没有创建 // 浏览器参数设置 const customArguments [-…

Mac book air 重新安装系统验证显示 untrusted_cert_title

环境&#xff1a; Mac Book Air macOS Sierra 问题描述&#xff1a; Mac book air 重新安装系统验证显示 untrusted_cert_title 解决方案&#xff1a; 1.终端输入命令行输入 date 会看到一个非常旧的日期 2.更改日期为当前时间 使用以下命令来设置日期和时间&#xff1a…

java黑马学习笔记

数组 变量存在栈中&#xff0c;变量值存放在堆中。 数组反转 public class test{public static void main(String[] args){//目标&#xff1a;完成数组反转int[] arr {10,20,30,40,50};for (int i 0,j arr.length - 1;i < j;i,j--){int tep arr[j]; //后一个值赋给临时…

20240119-子数组最小值之和

题目要求 给定一个整数数组 arr&#xff0c;求 min(b) 的总和&#xff0c;其中 b 的范围涵盖 arr 的每个&#xff08;连续&#xff09;子数组。由于答案可能很大&#xff0c;因此返回答案模数 Example 1: Input: arr [3,1,2,4] Output: 17 Explanation: Subarrays are [3]…

Vision Transformer(VIT)模型介绍

计算机视觉 文章目录 计算机视觉Vision Transformer&#xff08;VIT&#xff09;Patch EmbeddingsHybrid ArchitectureFine-tuning and higher resolutionPyTorch实现Vision Transformer Vision Transformer&#xff08;VIT&#xff09; Vision Transformer&#xff08;ViT&am…

PACS医学影像采集传输与存储管理、影像诊断查询与报告管理系统,MPR多平面重建

按照国际标准IHE规范&#xff0c;以高性能服务器、网络及存储设备构成硬件支持平台&#xff0c;以大型关系型数据库作为数据和图像的存储管理工具&#xff0c;以医疗影像的采集、传输、存储和诊断为核心&#xff0c;集影像采集传输与存储管理、影像诊断查询与报告管理、综合信息…

4D毫米波雷达——FFT-RadNet 目标检测与可行驶区域分割 CVPR2022

前言 本文介绍使用4D毫米波雷达&#xff0c;实现目标检测与可行驶区域分割&#xff0c;它是来自CVPR2022的。 会讲解论文整体思路、输入数据分析、模型框架、设计理念、损失函数等&#xff0c;还有结合代码进行分析。 论文地址&#xff1a;Raw High-Definition Radar for Mu…

韵达快递单号查询入口,对需要的快递单号记录进行颜色标记

选择一款好的工具&#xff0c;往往能事半功倍&#xff0c;【快递批量查询高手】正是你物流管理的得力助手。它不仅可以助你批量查询快递单号的物流信息&#xff0c;还能帮你对需要的快递单号记录进行标记&#xff0c;让你享受高效便捷的物流管理体验。 所需工具&#xff1a; …

设计模式之迪米特法则:让你的代码更简洁、更易于维护

在软件开发中&#xff0c;设计模式是解决常见问题的最佳实践。其中&#xff0c;迪米特法则是一种非常重要的设计原则&#xff0c;它强调了降低对象之间的耦合度&#xff0c;提高代码的可维护性和可重用性。本文将介绍迪米特法则的概念、重要性以及在实际项目中的应用。 一、迪…

【微服务】springcloud集成sleuth与zipkin实现链路追踪

目录 一、前言 二、分布式链路调用问题 三、链路追踪中的几个概念 3.1 什么是链路追踪 3.2 常用的链路追踪技术 3.3 链路追踪的几个术语 3.3.1 span ​编辑 3.3.2 trace 3.3.3 Annotation 四、sluth与zipkin概述 4.1 sluth介绍 4.1.1 sluth是什么 4.1.2 sluth核心…

使用Ultimate-SD-Upscale进行图片高清放大

之前我们介绍过StableSR进行图片高清放大&#xff0c;如果调的参数过大&#xff0c;就会出现内存不足的情况&#xff0c;今天我们介绍另外一个进行图片高清放大的神器Ultimate-SD-Upscale&#xff0c;他可以使用较小的内存对图像进行高清放大。下面我们来看看如何使用进行操作。…

总线协议:GPIO模拟SMI(MDIO)协议:SMI协议介绍

0 工具准备 TN1305 Technical note IEEE802.3-2018 STM32F4xx中文参考手册 1 SMI介绍 1.1 SMI总体框图 站管理接口SMI&#xff08;Serial Management Interface&#xff09;&#xff0c;也可以称为MDIO接口&#xff08;Management Data Input/Output Interface&#xff09;。…

C语言——内存函数介绍和模拟实现

之前我们讲过一些字符串函数&#xff08;http://t.csdnimg.cn/ZcvCo&#xff09;&#xff0c;今天我们来讲一讲几个内存函数&#xff0c;那么可能有人要问了&#xff0c;都有字符串函数了&#xff0c;怎么又来个内存函数&#xff0c;这不是一样的么&#xff1f; 我们要知道之前…