C语言之详解预处理

前言:

预处理也叫预编译,是编译代码时的第一步,经过预处理后生成一个.i文件,如果不明白编译与链接作用的小伙伴可以先看看博主的上一篇博客—— ,不然知识连贯性可能会显得很差哦。

正文目录:

  1. 预定义符号
  2. #define定义常量
  3. #define定义宏
  4. 带有副作用的宏参数
  5. 宏替换的规则
  6. 宏与函数的对比和命名约定
  7. #和##
  8. #undef
  9. 条件编译
  10. 头文件的包含
  11. 其他预处理指令......

1.预定义符号

如下为c语言中的预定义符号:

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

这些都是我们可以直接使用、在预处理阶段就已经处理了的。

我们举个例子:

2.#define定义常量

基本语法形式如下:

#define name stuff        //其中name表示名字, stuff表示(被替换的)内容

当我们用该语法后,stuff就被name给替换了。

示例如下:

此时100就被M替换了,因此a输出结果为100。

在预处理阶段,会将#define定义的“名字”替换为它所表示的常量,比如说上图中的a = M ,经预处理后实际的代码形式为a = 100

3.#define定义宏

如下为宏的申明方式:

#define name( parament-list ) stuff   //parament-list为参数列表,stuff为内容。

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

示例如下:

但是需要注意,#define定义宏可能会带来运算级优先级的问题。

就跟上面同样的题目,我们换种写法如下:

可以发现,我们M(a)中的a本来为10,但当我们写成9 + 1后结果与我们所期待的不符。

这是为什么呢?——上文讲过,“在预处理阶段,会将#define定义的“名字”替换为它所表示的常量”,此处的“名字”就相当于M(9 + 1)。当替换后,该行代码就变为了“a = 9 + 1 * 9 + 1”。

容易发现跟我们的要求不符,这就是运算符优先级的问题。

我们可以这样修改:

如上图所示,我们加个小括号就可以了。因此我们用#define定义宏时,一定不要吝啬括号

4.带有副作用的宏参数

简单来讲副作用就是说会导致参数发生改变

严谨点的就是下面的说法:

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

例如:

x + 1;      //不带副作⽤
x++;       //带有副作⽤

具体示例如下:

当我们后续再使用代码中的a、b时,就可能不是我们所期望的值了。

5.宏替换的规则

这个就是偏概念性的东西了,规则如下:

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

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

我们给出一个示例,按照宏替换的规则进行替换,如下图所示:

然后我们就将第二个宏定义的内容替换到主函数中的相应宏中,

因此最终主函数的第一行代码被替换为了:int m = ((a) > (b) ? (a) : (b));

6.宏与函数的对比和命名约定

我们先讲二者的命名约定:

容易发现,函数的宏的使⽤语法很相似。所以语言本⾝没法帮我们区分⼆者。
因此我们平时的⼀个习惯是:
把宏名全部⼤写
函数名不要全部⼤写

宏与函数的对比

宏一般用于简单的运算,因为如果简单的运算就使用函数的话会加大我们的计算时间。

因此宏相对于函数:

宏在程序的规模和速度更胜一筹(规模更小,运算更快)

不仅如此,宏的参数与类型无关

而宏较于函数的劣势处:

  1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。
  2. 宏是无法调试的
  3. 参数与类型无关,因此不够严谨
  4. 可能带来运算符优先级问题,容易出错

但是宏却可以做到一些函数永远做不到的事——比如说参数中出现类型。

两者具体差异如下表所示:

7.#和##

#运算符

#可以理解为“字符串化

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

示例如下:

##运算符

##可以理解为“联结符号

## 被称为记号粘合,它可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。 这样的连接必须产⽣⼀个合法的标识符,否则其结果就是未定。

示例如下:

但是这样就有点麻烦了 一次求值就要写一个函数 因此我们可以换种方式写👇:

//假设我们要求两个数中的较大值
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x > y ? x : y); \
}

GENERIC_MAX(int)   //替换到宏体内后int##_max 生成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max 生成了新的符号 float_max做函数名

int main()
{
	//调用函数
	int m = int_max(2, 3);
	printf("%d\n", m);

	float f = float_max(3.5f, 4.5f);
	printf("%f\n", f);
	return 0;
}

这样子 我们就可以直接通过宏来求 而不用每次求不同类型的数据时都要写不同的函数。

8.#undef

移除一条宏定义

示例如下:

9.条件编译

简单来讲就是“满足条件就编译,不满足条件就不编译”。


像调试性的代码,删除可惜,保留⼜碍事,我们就可以选择性的编译。

示例如下:

其中#if#endif就是条件编译语句。易知1 != 2 因此没有打印hehe。

10.头文件的包含

头文件的包含一般分为两种形式:

  1. <······>   (如#include <stdio.h>)
  2. "······"   (如#include "filename")

前者库文件包含,一般指标准库中头文件的包含。

查找头文件直接去标准路径下去查找,如果找不到就提⽰编译错误。
这样是不是可以说,对于库⽂件也可以使⽤" "的形式包含呢?——
答案是肯定的,可以是可以,但是这样做查找的效率就低些,而且这样也不容易区分是库⽂件还是本地文件了。

后者本地文件包含,一般指自己创建的头文件的包含。

查找策略:先在源文件所在⽬录下查找,如果该头文件未找到,编译器就像查找库函数头文件⼀样在标准位置查找头⽂件。
如果还找不到就提⽰编译错误。

除了上述两种外,其实还有一种情况——嵌套文件包含

#include 指令可以使另外⼀个⽂件被编译,就像它实际出现于 #include 指令的
地⽅⼀样。
这种替换的⽅式很简单:预处理器先删除这条指令,并用包含⽂件的内容替换
⼀个头⽂件被包含10次,那就实际被编译10次,如果重复包含,对编译的压⼒就⽐较⼤。

比如下面的代码:

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
 
 return 0;
}

其中的十条#include "test.h",在编译时都被test.h头文件中包含的内容替换了,导致编译压力较大。 这种情况就是嵌套文件包含。

那么我们该如何解决这些问题呢?——自然是用刚刚学的条件编译了。

1.每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif 
//__TEST_H__

2.或者

 #pragma once

这样就可以有效避免头文件的重复引入了。

11.其他预处理指令

#error
#pragma
#line
...
...

#pragma pack()  

我们还有很多其他的预处理指令,本文自然不可能给大家一 一讲完,大家可以自行去了解哦~



创作不易,如果作者写的还行的话给个免费的三连吧亲😙😙

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

discuz迪恩cul!教育课程培训网站模板

Discuz x3.2模板 迪恩cul!教育课程培训 GBK&#xff0c;程序包中内附详细的安装教程&#xff0c;下载后按照教程安装即可 discuz迪恩cul!教育课程培训网站模板

qemu 安装ubuntu22.04虚拟机 -纯命令行-可ssh-带网络-编译安装 linux kernel-编译安装 kernel module

tar -xjf xxx.tar.bz2 1&#xff0c;预备系统盘数据 1.1 下载光盘 注意需要 liver-server $ wget https://releases.ubuntu.com/22.04.4/ubuntu-22.04.4-live-server-amd64.iso 1.2 挂载并拷贝 $ sudo mkdir /mnt/iso_ubuntu-22.04.4-live-server-amd64 $ sudo mount u…

星闪指向遥控,做家电交互的破壁人

“面壁者罗辑&#xff0c;我是你的破壁人。” 科幻小说《三体》中&#xff0c;当人类的基础科学被三体人封锁&#xff0c;变得停步不前&#xff0c;人类启动了自救的面壁计划&#xff0c;通过一次又一次破壁&#xff0c;找到战胜三体人的办法。 现实中&#xff0c;有一点已经成…

html--好看的手机充值单页

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>线上充值-首页</title><meta content"widthdevice-width,initial-scale1.0,maximum-scale1.0,user-scalable0" name"viewport&…

【Vue-Vben-Admin】1、初次运行和介绍

【Vue-Vben-Admin】1、初次运行和介绍 Vben-Admin 初次运行和介绍 小小的介绍规定版本文件树安装依赖运行项目 小小的介绍 一款 Vue3 Typescript4 Vite2 后台管理项目&#xff0c;功能挺多的&#xff0c;还有组件库 规定版本 此个人文档规定版本为 2.8.0&#xff0c;可能版本…

AI大模型企业应用实战(16)-langchain核心组件

1 stuff 将文档列表插入到提示词中&#xff0c;适合文档较小或少量文档的应用。 2 refine 通过循环输入文档并迭代更新答案来构建响应&#xff0c;一次只传递给LLM一个文档&#xff0c;适合LLM上下文大小不能容纳的小文档。 参考&#xff1a; https://js.langchain.com/v0.1…

QT中利用qss来创建一个圆角矩形窗口,并利用Qt::WA_TranslucentBackground属性解决留白问题

1、效果 2、实现 QWidget#centralwidget {border-radius: 30px solid default;border-image: url(:/images/bk<

【Golang - 90天从新手到大师】Day06 - 数组

系列文章合集 Golang - 90天从新手到大师 数组是golang中最常用的一种数据结构,数组就是同一类型数据的有序集合 定义一个数组 格式: var name [n]type n为数组长度,n>0 且无法修改,type为数组的元素类型如: var a [2]int上面的例子定义了一个长度为2,元素类型为int的数组…

MySQL数据库(三):读取数据库数据

上一节&#xff0c;我们介绍了数据库的基本操作&#xff0c;以及最后演示了如何使用库来连接数据库&#xff0c;在实际应用中&#xff0c;我们通常需要按照指定的条件对数据库进行操作&#xff0c;即增删改查操作&#xff0c;这是非常重要的&#xff01;这一节我们继续通过一个…

Open3D 点云FPS最远点下采样

目录 一、概述 二、代码实现 2.1实现原理 2.2完整代码 三、实现效果 3.1原始点云 3.2采样后点云 3.3数据对比 一、概述 最远点采样&#xff08;Farthest Point Sampling, FPS&#xff09;是一种有效的下采样方法&#xff0c;用于从大量点云数据中选择具有代表性的子集。…

Fine-tuning在垂直领域的最佳实践指南

对于Fine-tuning是深度学习和机器学习领域一个特别重要的概念&#xff0c;并且每个企业的实践方式也会有所不同&#xff0c;今天我们就来聊一聊Fine-tuning。 什么是Fine-tuning Fine-tuning指的是模型微调&#xff0c;通常是指在一个预训练模型的基础上&#xff0c;通过在特…

redis数据库备份,回档,迁移工具使用

相信很多服务器开发者或者运维同学都遇到过这样的场景,比如我要从阿里云的redis集群把数据迁移到另外一个账号下的或者另外一个云服务商的redis集群,或者单机,或者哨兵模式redis实例,那么这种情况下我推荐给大家一款非常好用的工具,可以无缝的从A集群/单机/哨兵/已有的数据…

安装虚拟环境

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Flask依赖两个外部库&#xff1a;Werkzeug和Jinja2。Werkzeug是一个WSGI&#xff08;在Web应用和多种服务器之间的标准 Python 接口&#xff09;工具…

频率和时间单位换算表

1. 频率单位和计数单位的换算关系 1.1 频率换算关系 1 MHz 等于 10^6 Hz 1.2 常用计数单位及其换算关系 1000 K 1 M 1000 M 1 G 分别表示&#xff1a; K (千) 10^3 M (兆) 10^6 G (吉) 10^9 2. 时间与频率的关系 频率&#xff08;Frequency&#xff09;和周期&#xff0…

Pytest和Unitest框架对比

在学到自动化的时候,很多同学都遇到了Pytest和Unitest框架,有的人是两个都学,但是学的不精只是知道分别怎么用.不了解两个区别是什么.有的是犹豫到底要学习那个框架.其实要做好自动化测试,是有必要了解不同框架之间的差异化的. Pytest 特点: Pytest采用了更简洁、更灵活的语法…

使用Let‘s Encrypt 申请通配符证书

为什么不使用阿里云/腾讯云等公有云厂商提供的免费证书? 上篇介绍了从阿里云上面申请免费证书,有效期一年 为网站配置https证书 公有云提供的证书不支持通配符&#xff0c;只支持某个确定的解析。 不管是二级域名还是三级域名&#xff0c;只要是具体的确定的地址&#xff0c;都…

记MySQL事务+消息队列引起的问题

问题描述&#xff1a; 先说一下流程&#xff1a;后端保存前端提交的图表信息&#xff0c;然后发送异步消息到消息队列&#xff0c;由下游服务去处理图表信息。 部署项目到服务器&#xff0c;验证项目功能的时候&#xff0c;出现了以下错误&#xff1a;数据库存在数据。下游服…

【服务器07】之【GitHub项目管理】及【Unity异步加载场景】

登录GitHub官网 GitHub: Let’s build from here GitHub 注册账号 登录账号 输入一个自定义名字&#xff0c;点击创建存储库就可以了 现在我们下载Fork Fork - a fast and friendly git client for Mac and Windows (git-fork.com) 免费的 下载完成之后点击File下的Clone …

黑苹果EFI详细配置说明

先上网址: https://dortania.github.io/OpenCore-Install-Guide/installer-guide/opencore-efi.html 1: 了解作用 ACPI作用: 总结: ACPI是UEFI引导方式和操作系统之间的硬件抽象接口,概述了硬件设备,如 USB 控制器、CPU 线程、嵌入式控制器、系统时钟等 硬件设备识别和驱动:…

Nacos 2.x 系列【15】数据源插件支持达梦、Oracel、PostgreSQL......

文章目录 1. 概述2. 持久层机制2.1 固定语句2.2 数据源插件 3. 案例演示3.1 编译已实现插件3.2 自定义插件3.3 数据库初始化3.4 插件引入3.4.1 方式一&#xff1a;引入到源码3.4.2 方式二&#xff1a;插件加载目录 3.5 修改配置3.6 测试 1. 概述 在实际项目开发中&#xff0c;…