详解预处理

news2024/9/27 5:58:07

全文目录

  • 前言
  • 预定义符号
  • `#define` 定义标识符常量
  • `#define` 定义宏
    • `#define` 替换规则
    • `#` 宏参数转换字符串
    • `##` 宏参数拼接
    • 带有副作用的宏参数
  • 宏与函数的对比
  • `#undef` 移出宏定义
  • 命令行定义
  • 条件编译
  • `#include` 文件包含
    • 头文件的包含方式
    • 头文件的重复包含

前言

前面我们学习了程序的编译和链接的大致流程,其中编译、汇编、链接太过深奥,只需要了解流程即可。但是预编译中的文本操作需要深入了解一下。

预定义符号

C语言中有内置的预定义符号包括main、关键字、库函数等。除了这些还有一些日志信息:

__func__ //当前调用的函数
__FILE__ //进行编译的源文件(绝对路径)
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

这些符号可以很好的帮我们输出当前程序的日志信息,便于日后维护代码。

// demo
printf("file:%s\tline:%d \tdate:%s\ttime:%s\n", 
		__FILE__, __LINE__, 
		__DATE__, __TIME__);

#define 定义标识符常量

语法:

#define name stuff
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外
// 每行的后面都加一个反斜杠(续行符)。

在预编译阶段就会将文件中的全部name 替换成 stuff

#define结尾可以加上;,加上语句就是多了一条空语句,但是一般是不加上;,容易引发语法错误。

比如上面日志信息的输出就可以使用 #define 定义一个表示符,节省代码量(而且看起来很牛的样子)。

// demo

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
						date:%s\ttime:%s\n" ,\
						__FILE__,__LINE__ , \
						__DATE__,__TIME__ )
DEBUG_PRINT;

在VS2019中,可以通过以下设置将生成预处理的文件,但是设置后就不能运行程序了,在看完预处理文件后需要重置该设置

在这里插入图片描述

打开预处理的文件就是以下内容:

printf("file:%s\tline:%d\t date:%s\ttime:%s\n" , "D:\\code\\daily_code\\FlexibleArray\\FlexibleArray\\test.c",13 , "Apr 14 2023","09:13:00" );

#define 定义宏

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

定义语法:

#define name( parament-list ) stuff
//其中的parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:

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

宏的语法和函数的语法十分相似,所以在定义宏和函数的时候尽量做到命名约定:

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

//demo
#define MUL(a, b) a * b
int a = 5;
int b = 15;
int c = MUL(a, b);  // 75

这样看来宏是很简单的,那么再来看一组实例

//demo 
#define MUL(a, b) a * b
int a = 5;
int b = 15;
int c = MUL(a + 5, b + 5);  // 200 ?   85!

这是因为宏是不加任何处理,直接将参数替换成对应的值,运算符的优先级就会导致结果与预期的结果不一致。所以需要为宏的每一位参数加上括号

// 修正
#define MUL(a, b) (a) * (b)

再来看一组示例:

#define DOUBLE(x) (x) + (x)
int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

看上去,好像打印100,但事实上打印的是55.

解决方法:将宏整体加上括号

// 修正
#define MUL(a, b) ((a) * (b))

总结:

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

#define 替换规则

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

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

注意:

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

# 宏参数转换字符串

# 的作用:

把一个宏参数变成对应的字符串。

如果想在宏中让参数不被对应的值替换,直接使用参数名时,就可以使用 #

//demo
#define PRINT(n) printf(#n"'s value is %d\n", n)
int a = 10;
int b = 20;
PRINT(a); 	// a's value is 10
PRINT(b); 	// b's value is 20

如果想让宏能够打印不同类型的值,可以将宏的参数类型作为字符串进行打印

#define PRINT(FORMAT, VALUE) printf("the value is "FORMAT"\n", VALUE);

PRINT("%d", 10);
PRINT("%lf", 3.100);

## 宏参数拼接

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

简单来说就是将两边的宏参数作为字符串拼接起来:

//demo
#define CAT(girl, friend) girl##friend
int grilfriend = 18;
printf("%d\n", CAT(girl, friend)); // 18

带有副作用的宏参数

所谓的带有副作用的参数:表达式求值的时候出现的永久性效果。

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

MAX宏可以证明具有副作用的参数所引起的问题。

//demo 
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

对于预处理之后的表达式:

z = ( (x++) > (y++) ? (x++) : (y++));

可以很容易得到输出结果:

x=6 y=10 z=9

所以我们在使用宏时,对于参数的选择需要小心谨慎

宏与函数的对比

宏因为是文本替换,所以适合用来做一些小型计算,一个MAX 就可以看出宏的优点:

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

宏的优点:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。

宏是类型无关的。 并且宏的参数可以出现类型

//demo
#define MALLOC(num, type) (type*)malloc(num * sizeof(type))

既然有优点就一定会有缺点,宏的缺点:

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

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题和参数的副作用,导致程容易出现错。

总结对比:

在这里插入图片描述

#undef 移出宏定义

如果需要重定义一个宏,则需要用到#undef

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除,否则会报警告

命令行定义

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

但是在一般的集成开发环境中这个好像不能不能使用,在Linux的gcc中可以使用,只需要在编译的时候加上 -D 选项即可。

#include <stdio.h>
int main()
{
	int array [ARRAY_SIZE];
	int i = 0;
	for(i = 0; i< ARRAY_SIZE; i ++)
	{
		array[i] = i;
	}
	for(i = 0; i< ARRAY_SIZE; i ++)
	{
		printf("%d " ,array[i]);
	}
	printf("\n" );
return 0;
}

编译指令:

gcc -D ARRAY_SIZE=10 test.c

条件编译

条件编译的使用方法跟if else语句相似,不同的是条件编译时由预处理器求值的,所以只能使用一些常量。

常见的条件编译指令:

1.
#if 常量表达式
//...
#endif

//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif


2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif


3.判断是否被定义
#if defined(symbol) // 是
#if !defined(symbol) // 否

#ifdef symbol // 是
#ifndef symbol // 否


4.嵌套指令
#if defined(OS_UNIX)
	#ifdef OPTION1
		unix_version_option1();
	#endif
	
	#ifdef OPTION2
		unix_version_option2();
	#endif
#elif defined(OS_MSDOS)
	#ifdef OPTION2
		msdos_version_option2();
	#endif
#endif

这一点在库文件中十分常见,对于跨平台的文件有着很强的实用性。

#include 文件包含

在编码过程的第一步通常是#include <stdio.h> 这样的文件包含,使得stdio.h 参与编译,在之前的预编译已经知道了,其实就是头文件的内容替换,预处理器先删除这条指令,并用包含文件的内容替换。如果一个文件被包含10次,就会替换10次。

头文件的包含方式

在编码中有两种包含头文件的方式:

一种是本地文件包含:

#include "test.h"

该方式的查找策略:

先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。
如果找不到就提示编译错误。

一种是库文件的包含:

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

库文件的包含当然也可以使用本地文件被包含方式,但是效率会更低一些,所以还是遵守规则好些。

头文件的重复包含

在正常的编码中肯定是很少出现头文件的重复包含,但是很多情况是在我们不经意间发生的, 如:

在这里插入图片描述

如果不加处理test.c 中就包含了两份comm.h。虽然没什么大问题,但是每一次重复包含都会造成代码冗余。

处理方法:

每个头文件的开头写:
1. 
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__

2. 
#pragma once

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

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

相关文章

【C语言基础】源文件与头文件详解

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

Office Tool Plus 使用与激活

Office Tool Plus 一个强大且实用的 Office 部署工具。 可以免费激活使用office各种版本 体验了一下&#xff0c;觉得很不错&#xff0c;我介绍一下我使用的方式&#xff1a; 1.安装软件 访问官网&#xff1a;Office Tool Plus 选择ZIP的包&#xff0c;下载后解压&#xff0c…

Huggingface托管机器学习模型及API提供

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我想在我的网络和移动应用程序中使用机器学习模型&#xff0c;但要做到这一点&#xff0c;我必须在某个地方托管我的机器学习应用程序。 托管预先训练的 ML 模型称为推理。 我只想添加一些 Python ML 代码并快速获得 REST…

一段简单的汇编语言源程序【2】

此文章主要记录代码的编写&#xff0c;编译&#xff0c;连接&#xff0c;调试过程&#xff0c;相关工具的安装和使用介绍在前面的文章中已提供。 主要功能通过栈实现两个数的交换 源代码如下&#xff1a; assume cs:codesg codesg segmentmov ax,2000Hmov ss,axmov sp,0add s…

国产发力,长存入局|相变存储器PCM是SCM的未来吗?

在去年7月份有一件震惊存储圈的事情&#xff0c;那就是Intel说要放弃Optane产品线&#xff0c;包括PMEM和SSD两个方向都要放弃。存储圈看到听到这个消息也是一脸的茫然。 在Optane产品发布之前&#xff0c;大家针对DRAM和SSD之间的性能gap一直在苦苦找寻合适的产品。SCM存储级内…

深度学习12:胶囊神经网络

目录 研究动机 CNN的缺陷 逆图形法 胶囊网络优点 胶囊网络缺点 研究内容 胶囊是什么 囊间动态路由算法 整体框架 编码器 损失函数 解码器 传统CNN存在着缺陷&#xff08;下面会详细说明&#xff09;&#xff0c;如何解决CNN的不足&#xff0c;Hinton提出了一种对于图…

一篇掌握BFD技术(二):OSPF与BFD联动配置

1. 实验目的 熟悉OSPF与BFD联动的应用场景掌握OSPF与BFD联动的配置方法 想要华为数通配套实验拓扑和配置笔记的朋友们点赞关注&#xff0c;评论区留下邮箱发给你&#xff01; 2. 实验拓扑 3. 实验步骤 1&#xff09;IP地址的配置 AR1的配置 <Huawei>system-view…

Origin软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Origin是一款专业的科学绘图和数据分析软件&#xff0c;由美国OriginLab公司开发。它提供了丰富的数据分析和绘图工具&#xff0c;适用于各种科学领域&#xff0c;如生物学、化学、物理学、医学、地球科学等。Origin软件的主要特…

使用 S3 生命周期精确管理对象生命周期

在亚马逊工作这些年,我发现 S3 的生命周期配置是管理对象生命周期的重要但复杂的工具。在这篇文章中,我将利用实战经验,深入剖析生命周期,从核心概念到实际应用。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活…

C语言暑假刷题冲刺篇——day5

目录 一、选择题 二、编程题 &#x1f388;个人主页&#xff1a;库库的里昂 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C语言每日一练✨相关专栏&#xff1a;代码小游戏、C语言初阶、C语言进阶&#x1f91d;希望作者…

程序的编译链接【编译链接大概步骤】

全文目录 &#x1f600; 前言&#x1f642; 翻译环境和执行环境&#x1f636; 编译和链接&#x1f635;‍&#x1f4ab; 预编译&#xff08;预处理&#xff09;&#x1f635;‍&#x1f4ab; 编译&#x1f635;‍&#x1f4ab; 汇编&#x1f635;‍&#x1f4ab; 链接 &#x1…

数值类特征

数值类特征 数值类特征是最常见的一种特征类型&#xff0c;数值可以直接喂给算法。 为了提升效果&#xff0c;我们需要对数值特征做一些处理&#xff0c;本文介绍了4种常见的处理方式&#xff1a;缺失值处理、二值化、分桶、缩放。 什么是数值类特征&#xff1f; 数值类特征就是…

腾讯云服务器地域和可用区详细介绍_选择攻略

腾讯云服务器地域有什么区别&#xff1f;怎么选择比较好&#xff1f;地域选择就近原则&#xff0c;距离地域越近网络延迟越低&#xff0c;速度越快。关于地域的选择还有很多因素&#xff0c;地域节点选择还要考虑到网络延迟速度方面、内网连接、是否需要备案、不同地域价格因素…

Redis数据类型全总结【超详细万字总结】

文章目录 前言一、String1、内部实现2、应用场景缓存对象常规计数分布式锁共享 Session 信息 3、常用指令 二、List1、内部实现2、应用场景消息队列List 作为消息队列有什么缺陷 3、常用指令 三、Hash1、内部实现2、应用场景缓存对象购物车 3、常用指令 四、Set1、内部实现2、应…

Linux学习之LNMP环境搭建

LNMP是Linux、Nginx、MySQL和PHP的简称。 Linux参数显示 cat /etc/redhat-release看到操作系统是CentOS Linux release 7.6.1810&#xff0c;uname -r看到内核版本是3.10.0-957.el7.x86_64。 nginx安装 可以参考《Linux学习之CentOS 7源码安装openresty》 安装mariadb数据…

最简单的电子宣传册制作软件

HI&#xff01;今天给大家分享一款操作起来最简单的制作电子宣传册的软件---FLBOOK&#xff0c;它可支持在线排版编辑、一键套用模板的方式&#xff0c;使新手也能制作出优秀的电子宣传册。 具体如何制作的呢&#xff1f; 1.打开FLBOOK&#xff0c;进入FLBOOK主页 2.打开模板专…

在Jupyter中使用AI写代码,如有神助,太惊艳了

昨晚看到一个可以在JupyterLab中使用的AI代码辅助工具jupyter-ai&#xff0c;它的交互确实非常棒&#xff0c;可以直接聊天&#xff0c;也可以就笔记中的代码提问&#xff0c;最出彩的是生成笔记功能&#xff0c;还是蛮惊艳的。 这里就极简介绍一下安装及用法 第一步是创建环…

学习笔记:Pytorch利用MNIST数据集训练生成对抗网络(GAN)

2023.8.27 在进行深度学习的进阶的时候&#xff0c;我发了生成对抗网络是一个很神奇的东西&#xff0c;为什么它可以“将一堆随机噪声经过生成器变成一张图片”&#xff0c;特此记录一下学习心得。 一、生成对抗网络百科 2014年&#xff0c;还在蒙特利尔读博士的Ian Goodfello…

学习笔记230827--vue项目中,子组件拿不到父组件异步获取数据的问题

问题描述 父组件的数据是请求后台所得&#xff0c;因为是异步数据&#xff0c;就会出现&#xff0c;父组件的值传递过去了&#xff0c;子组件加载不到&#xff0c;拿不到值的问题。 下面从同步数据传递和异步数据传递开始论述问题 1. 父组件传递的是同步数据 父组件 <…

【Spring】什么是 AOP(面向切面编程) ? 为什么要有 AOP ? 如何实现 Spring AOP ?

文章目录 前言一、什么是 AOP ?二、为什么要使用 AOP ?三、 AOP 的组成四、Spring AOP 的实现1, 添加依赖2, 定义切面3, 定义切点4, 定义通知5, 创建连接点 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法…