当前位置: 首页 > news >正文

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语言深度解剖》中预处理一章

相关文章:

C语言·预处理详解

1. 预定义符号 C语言设置了一些预定义符号&#xff0c;可以直接使用&#xff0c;预定义符号也是在预处理期间处理的 __FILE__ 进行编译的源文件 __LINE__ 文件当前的行号 __DATE__ 文件被编译的日期 __TIME__ 文件被编译的时间 __STDC__ 如果编译器遵循ANSI C&#xff0c;…...

服务器与普通电脑的区别,普通电脑可以当作服务器用吗?

服务器在我们日常应用中非常常见&#xff0c;手机APP、手机游戏、PC游戏、小程序、网站等等都需要部署在服务器上&#xff0c;为我们提供各种计算、应用服务。服务器也是计算机的一种&#xff0c;虽然内部结构相差不大&#xff0c;但是服务器的运行速度更快、负载更高、成本更高…...

数字身份所有权:Web3时代用户数据的掌控权

随着Web3时代的来临&#xff0c;数字身份的概念正焕发出崭新的光芒。在这个数字化的时代&#xff0c;用户的个人数据变得愈加珍贵&#xff0c;而Web3则为用户带来了数字身份所有权的概念&#xff0c;重新定义了用户与个人数据之间的关系。本文将深入探讨Web3时代用户数据的掌控…...

python爬虫如何写,有哪些成功爬取的案例

编写Python爬虫时&#xff0c;常用的库包括Requests、Beautiful Soup和Scrapy。以下是三个简单的Python爬虫案例&#xff0c;分别使用Requests和Beautiful Soup&#xff0c;以及Scrapy。 1. 使用Requests和Beautiful Soup爬取网页内容&#xff1a; import requests from bs4 …...

PLC物联网网关BL104实现PLC协议转MQTT、OPC UA、Modbus TCP

随着物联网技术的迅猛发展&#xff0c;人们深刻认识到在智能化生产和生活中&#xff0c;实时、可靠、安全的数据传输至关重要。在此背景下&#xff0c;高性能的物联网数据传输解决方案——协议转换网关应运而生&#xff0c;广泛应用于工业自动化和数字化工厂应用环境中。 无缝衔…...

explain工具优化mysql需要达到什么级别?

explain工具优化mysql需要达到什么级别&#xff1f; 一、explain工具是什么&#xff1f;二、explain查询后各字段的含义三、explain查询后type字段有哪些类型&#xff1f;四、type类型需要优化到哪个阶段&#xff1f; 一、explain工具是什么&#xff1f; explain是什么&#x…...

RHCE作业

架设一台NFS服务器&#xff0c;并按照以下要求配置 1、开放/nfs/shared目录&#xff0c;供所有用户查询资料 2、开放/nfs/upload目录&#xff0c;为192.168.xxx.0/24网段主机可以上传目录&#xff0c;并将所有用户及所属的组映射为nfs-upload,其UID和GID均为210 3、将/home/to…...

在Java中调企微机器人发送消息到群里

目录 如何使用群机器人 消息类型及数据格式 文本类型 markdown类型 图片类型 图文类型 文件类型 模版卡片类型 文本通知模版卡片 图文展示模版卡片 消息发送频率限制 文件上传接口 Java 执行语句 String url "webhook的Url"; String result HttpReque…...

鸿蒙开发(四)UIAbility和Page交互

通过上一篇的学习&#xff0c;相信大家对UIAbility已经有了初步的认知。在上篇中&#xff0c;我们最后实现了一个小demo&#xff0c;从一个UIAbility调起了另外一个UIAbility。当时我提到过&#xff0c;暂不实现比如点击EntryAbility中的控件去触发跳转&#xff0c;而是在Entry…...

K8s(七)四层代理Service

Service概述 Service在Kubernetes中提供了一种抽象的方式来公开应用程序的网络访问&#xff0c;并提供了负载均衡和服务发现等功能&#xff0c;使得应用程序在集群内外都能够可靠地进行访问。 每个Service都会自动关联一个对应的Endpoint。当创建一个Service时&#xff0c;Ku…...

鼎捷软件获评国家级智能制造“AAA级集成实施+AA级咨询设计”供应商

为贯彻落实《“十四五”智能制造发展规划》&#xff0c;健全智能制造系统解决方案供应商(以下简称“供应商”)分类分级体系&#xff0c;推动供应商规范有序发展&#xff0c;智能制造系统解决方案供应商联盟组织开展了供应商分类分级评定(第一批)工作&#xff0c;旨在遴选一批专…...

(循环依赖问题)学习spring的第九天

Bean实例的属性填充 Spring在属性注入时 , 分为如下几种情况 : 注入单向对象引用 : 如usersevice里注入userdao , userdao里没有注入其他属性 注入双向对象引用 : 如usersevice里注入userdao , userdao也注入usersevice属性 二 . 着重看循环依赖问题 (搞清原理即可) 问题提出…...

Kotlin的数据类

引言 我们在做项目中涉及到各种数据类的处理&#xff0c;很多很杂乱。难免一个人的知识点有盲点&#xff0c;所以想着做个整理。 定义 在平时的使用中&#xff0c;我们会用到一些类来保持一些数据或状态&#xff0c;我们习惯上成为bean或者entity&#xff0c;也有的定义为mod…...

PTA 7-13 统计工龄

给定公司N名员工的工龄&#xff0c;要求按工龄增序输出每个工龄段有多少员工。 输入格式: 输入首先给出正整数N&#xff08;≤105&#xff09;&#xff0c;即员工总人数&#xff1b;随后给出N个整数&#xff0c;即每个员工的工龄&#xff0c;范围在[0, 50]。 输出格式: 按工…...

算法常用思路总结

思路 1. 求数组中最大最小值思路代码 2. 计算阶乘思路&#xff1a;代码&#xff1a; 3. 得到数字的每一位思路代码 4. 计算时间类型5. 最大公约数、最小公倍数6. 循环数组的思想题目&#xff1a;猴子选大王代码 补充经典例题1. 复试四则运算题目内容题解 2. 数列求和题目内容题…...

Leetcode 3016. Minimum Number of Pushes to Type Word II

Leetcode 3016. Minimum Number of Pushes to Type Word II 1. 解题思路2. 代码实现 题目链接&#xff1a;3016. Minimum Number of Pushes to Type Word II 1. 解题思路 这道题的话思路其实还是蛮简单的&#xff0c;显然我们的目的是要令对给定的word在键盘上敲击的次数最小…...

node.js如何将webp转jpg图片

在Node.js中&#xff0c;可以使用一些库来实现将WebP图像转换为JPEG。一个常用的库是sharp&#xff0c;它是一个快速、高效的图像处理库。以下是一个简单的Node.js脚本示例&#xff0c;演示如何使用sharp库将WebP转换为JPEG&#xff1a; 首先&#xff0c;确保已经安装了sharp库…...

达梦数据库 忘记 SYSDBA 密码 处理方法

DM 提供数据库身份验证模式、基于操作系统的身份验证模式、外部身份验证模式和 UKEY 身份验证模式来保护对数据库访问的安全。数据库身份验证模式需要利用数据库口令&#xff0c; 即在创建或修改用户时指定用户口令&#xff0c;用户在登录时输入对应口令进行身份验证;基于操作 …...

SpringBoot ES 重建 Mapping

SpringBoot ES 重建 Mapping 1 复制数据2 删除老索引3 重建索引4 复制回数据 1 复制数据 POST http://elastic:123456127.0.0.1:9200/_reindex{"source": {"index": "老索引名称"},"dest": {"index": "备份索引名称&q…...

【51单片机】矩阵按键

0、前言 参考&#xff1a;普中 51 单片机开发攻略 1、硬件 2、软件 main.c #include <reg52.h> #include <intrins.h> #include "delayms.h"typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; #define GPIO_KEY P1 #d…...

Redis- AOF刷盘策略

在Redis中&#xff0c;appendfsync everysec 是一个与持久化相关的配置选项&#xff0c;它属于 Redis 的 AOF&#xff08;Append Only File&#xff09;持久化策略的一部分。 Redis支持两种主要的数据持久化方式&#xff1a;RDB&#xff08;快照&#xff09;和AOF&#xff08;…...

标量、向量、矩阵和张量的区别?

标量、向量、矩阵和张量是数学和物理学中常用的概念&#xff0c;它们在多维数据表示和处理中扮演着关键角色。下面是这些概念的基本区别&#xff1a; 标量&#xff08;Scalar&#xff09;: -标量是单个数字&#xff0c;用于表示单一的量。 -它没有方向。 -在数学中&#xff0…...

【51单片机】动态数码管

0、前言 参考&#xff1a; 普中51单片机开发攻略–A2.pdf 1、数码管介绍 上一章我们主要是介绍一位数码管的内部结构及控制原理。下面我们再来介 绍下多位数码管及动态显示原理的相关知识。 1.1 多位数码管简介 2、74HC245 和 74HC138 芯片介绍 2.1 74HC245 芯片简介 2.2 7…...

Webpack5入门到原理22:提升打包构建速度

HotModuleReplacement 为什么 开发时我们修改了其中一个模块代码&#xff0c;Webpack 默认会将所有模块全部重新打包编译&#xff0c;速度很慢。 所以我们需要做到修改某个模块代码&#xff0c;就只有这个模块代码需要重新打包编译&#xff0c;其他模块不变&#xff0c;这样…...

EXCEL VBA获取幸运数字号码

EXCELVBA获取幸运数字号码 以下就是VBA幸运号码产生的程序&#xff0c;复制粘贴到VBA代码框即可运行 Option Base 1 Sub 幸运号码()Dim n As Integer, i As Integer, j As IntegerDim l() As Integern Application.InputBox("请输入需要产生幸运号码的数量&#xff1a;…...

【标准IO】fseek函数、ftell函数、fflush函数、getline函数

目录 fseekftellrewindfflushgetline 橙色 当你在文件中写入了10个字符后&#xff0c;又想把这10个字符读出来&#xff0c;该怎么做呢&#xff1f;因为有文件操作符指针的存在&#xff0c;此时该指针已经指在了这10个字符末尾&#xff0c;所以需要把该指针重定向&#xff0c;这…...

VSCODE使用CMAKE显示命令无法找到

背景&#xff1a;使用了code server&#xff0c;安装CMAKE和CMAKE TOOLS&#xff0c;但是通过ctrlshiftp打开命令面板&#xff0c;运行随便一个cmake指令&#xff0c;都出现了指令无法找到。具体为“命令"CMake: 配置"导致错误 (command ‘cmake.configure’ not fou…...

【SpringCloud】微服务框架后端部署详细过程记录20240119

前言&#xff1a;前两天公司接到客户提供的一个微服务框架&#xff0c;导师让我在本地部署验证一下该框架的可用性&#xff0c;借此机会记录一下微服务项目的一个基本部署流程&#xff0c;仅供学习参考&#xff0c;如有不足还请指正&#xff01; 文件结构 提供的压缩文件共包含…...

《设计模式的艺术》笔记 - 桥接模式

介绍 桥接模式将抽象部分与其实现部分分离&#xff0c;使它们都可以独立地变化。它是一种对象结构型模式&#xff0c;又称为柄体模式或接口模式 实现 myclass.h // // Created by yuwp on 2024/1/12. //#ifndef DESIGNPATTERNS_MYCLASS_H #define DESIGNPATTERNS_MYCLASS_H#…...

Redis高并发分布式锁

目录 场景描述 订单扣减场景举例 代码调整1 代码调整2 代码调整3 redisson锁续命核心代码 场景描述 订单扣减场景举例 //首先在redis中set stock 300 RequestMapping("/deduct_stock") public String deductStock() {int stock Integer.parseInt(stringRedi…...