预处理/预编译详解(C/C++)

        在上一篇的bolg中的编译与链接中提到过预处理,但只是较为简单的讲解,本篇将会对预处理进行详细的讲解。

        其中在预处理中很重要的一个一个知识点是#define定义常量与宏,还区分了宏与函数的区别,以及#和##符号,还涉及条件编译头文件的包含等等。

        如果想看对应的讲解可以直接看文章旁边的目录。

1.预定义符号

        在预处理详解中,首先就是关于一些预定义符号,其中包含以下的预定义符号:

__FILE__   //进行编译的源文件的地址
__LINE__   //文件当前的行号
__DATA__   //文件被编译的日期
__TIME__   //文件被编译的时间
__STDC__   //编译器是否遵循ANSI C,是输出1,不是输出0(vs2022不支持/gcc环境支持)

        对于这些预定义符号的使用如下:

 2.#define定义常量

         在预处理中对于#define的处理也是直接将#define所定义的直接转换,特别是一些常量定义,#define对于常量定义的语法如下:

#define name stuff

        例如: 

#define MAX 1000
#define PRINT_DEBUG printf("%s\t%d\t%s\t%s\n",	__FILE__, __LINE__, __DATE__, __TIME__)  
//打印当前文件地址,当前代码行数,日期和时间
#define ElemType int     //将ElemType定义为int类型
#define forever for(;;)  //创建一个无限循环
#define reg register     //为register(寄存器)这个关键字,创建一个简短的名字

        如下:

        在预处理阶段都将对应的#define定义的直接替换掉,这也是在预处理阶段的一个特点,操作系统只会将#define定义的直接替换而不会做计算。 

 3.#define定义宏

        #define机制包括了一个规定,允许把参数替换到文本中(如上),这种实现通常被称为宏或者定义宏。以下为宏的申明方式:

#define name( parament-list ) stuff

        其中的 parament-list 为一个由逗号隔开的符号表,它们可能出现在stuff中。

        注:参数列表的左括号必须与name紧邻,不能存在空格,若出现空格,参数列表就会被解释为stuff的一部分。 

         以下举出一个简单的宏:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>#define SQUARE(X) X*X  //计算一个数的平方int main() {printf("%d\n", SQUARE(5));printf("%d\n", SQUARE(4+1));return 0;
}

        以下为计算结果: 

        如上,计算出的结果为25,9。那么为什么不是25,25呢,因为在预处理阶段,对于#define定义的宏,只是直接将参数进行替换,对于第一个计算的是5*5,而对于第二个计算的是4+1*4+1,所以计算出的结果为25,9。由这个例子也可以得出,对于宏的定义我们需要更严谨,所以在定义宏时,最好将每个参数都加上括号,防止被操作符的优先级所影响。所以我们对以上的可以修改为:

        这样可以使得宏的定义更加严谨。 但是这样的定义方式真的够严谨吗,在给出下面这个宏定义:

        计算出的结果为10和55,那么为什么不是10和100呢,是应为第二个计算的是10*5+5,所以我们不仅仅需要对每一个元素加上一个括号,我们还需要对计算出的结果加上一个括号,这样的计算结果才能保证真正的正确。 

4.带有副作用的宏参数

        对于以上的一些参数只要我们宏定义正确,那么就可以计算正确的结果,但是在宏定义中也存在一些一定会出现错误的形式,以下将列举:

#define MAX(X,Y) ((X)>(Y)?(X):(Y))int main() {int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);printf("%lf\n", MAX(9, 30.5));printf("%c\n", MAX(3, 'c'));return 0;
}

        以下为计算结果: 

        计算出的结果为什么x=6,y=10,z=9呢?还是按照预处理的步骤分析,先直接将x++与y++带入进去,然后在分别计算。 如如下步骤

        所以计算出的结果为z=9 x=6 y=10。在图中我们不仅发现宏计算出的结果,不会受到类型的限制,也就是说,关于宏的参数我们可以传入许多不同类型的参数。这也是宏很重要的一点。

5.宏替换的规则/宏与函数的区别

5.1宏替换的规则

        我们总结以上的规律可以得出,在程序中扩展#define定义符号和宏时,需要涉及以下结果步骤:

        1.在调用宏时,首先对参数进行检查,看看是否包含#define定义的符号如果是,那么这些符号首先被替换。

        2.替换文本随后被插入到原来文本的位置。对于宏,参数名称被他们的值所替换。

        3.再次对结果文件进行扫描,看看是否包含任何由#define定义的符合。如果发现,则重复以上流程。

        注:宏参数和#define可以出现在其他#define定义的符号,但是对于宏,不能出现递归

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

5.4宏与函数的对比

        宏通常被用于执行简单的运算,比如以上找最大值的运算,使用宏更有优势。

        那么为什么不使用函数来完成找最大值呢?如下:

        当我们分别用宏和函数求解出最大值的时候,我们发现在汇编代码中,宏下面只有一行,而在函数下面则由4行,所以说宏的计算会更少,对于一些逻辑简单的运算,我们可以用宏来实现。

        所以可以得出以下对比的几点:

        宏相对函数的优点

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

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

        3.宏有时候可以做到函数做不到的事情。宏的参数可以出现类型,但函数做不到,如下:

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))int main() {int* arr = MALLOC(10, int);if (arr == NULL) {perror("MALLOC:");return 1;}for (int i = 0; i < 10; i++) {arr[i] = i;}for (int i = 0; i < 10; i++) {printf("%d ",arr[i]);}return 0;
}

        宏相对函数的缺点

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

        2.宏不可以调试。

        3.宏由于类型无关,相对来说不够严谨。

        4.宏可能会带来运算符优先级的问题,导致程序出现错误。

6.#和##

6.1 #运算符

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

        #运算符所执行的操作可以理解为“字符串化”。

        如下操作:

        #运算符可以直接将#n替换为“a”,直接将整个字符串连接起来。运算出的代码就如以上的代码相同。

6.2 ##运算符

        ##运算符可以把位于它两边的符号合成一个符号,允许宏定义从分离的文本片段创建标识符。##符号被称为记号粘合。(就是将左右两边的符号联合在一起)。

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

        如下,实现一个函数求解2个数较大值,不同类型的数据类型写出不同的函数。

#define GENERIC_MAX(type)\      //“\”表示连接符,与下一行直接连接
type type##_max(type x,type y)\
{							\return x>y?x:y;			\
}GENERIC_MAX(int)
GENERIC_MAX(float)int main() {int m = int_max(2, 3);printf("%d\n", m);float n = float_max(3.5f, 4.5f);printf("%lf\n", n);return 0;
}

       

        通过如上的操作,我们就可以得出多个同结构的函数。但是这样的操作在实际的开放工程中使用的很少。 

 7.条件编译

        在编译一个程序的时候我们如果将一条(一组)语句编译或者放弃,我们可以使用条件编译指令。比如一些调试性语句,删除可惜,保留又碍事。我们就可以使用条件语句,如下:

// #define __DEBUG__ int main(){int i=0;int arr[10]={0};for(i=0;i<10;i++){arr[i]=i;#ifdef __DEBUG__    //用于判断__DEBUG__是否被定义,如果被定义则为真,反之为假printf("%d ",arr[i]);#endif}return 0;
}

        对于如上的结果,可以知道#ifdef与#endif的功能为判断一个宏是否被定义,如果被定义,则执行,如果没有被定义,则忽略 #ifdef与#endif 之间的代码。

        以下还有许多的定义,我就一下将其列举出来,不做过多的解释:

#define MAX 1int main() {int i = 0;int arr[10] = { 0 };for (i = 0; i < 10; i++) {arr[i] = i;#if 1           //常量表达式的条件编译printf("yes\n");#endif#if MAX==1      //多分支的条件编译printf("MAX=%d", 1);#elif MAX==2printf("MAX=%d", 2);#elseprintf("MAX=%d", 3);#endifprintf("MAX未知\n");#ifdef MAX       //判断是否被定义printf("MAX被定义");#endif#ifndef MAX      //判断是否没有被定义printf("MAX未被定义\n");#endif#if defined(MAX)  //嵌套定义#if MAX==1      //多分支的条件编译printf("MAX=%d", 1);#elif MAX==2printf("MAX=%d", 2);#elseprintf("MAX=%d", 3);#endifprintf("MAX未知\n");#elif defined(DEBUG)#ifdef MAX       //判断是否被定义printf("MAX被定义");#endif#ifndef MAX      //判断是否没有被定义printf("MAX未被定义\n");#endif#endif}return 0;
}

        其中值得注意的一点是:每一个条件编译都需要与一个#endif相对应。 

8.头文件的包含

8.1 头文件的包含

        头文件的包含包括本地头文件库文件的包含

        本地头文件的包含一般指的是由自己定义在当前路径的头文件,一般形式如下:

#include "filename"

        库文件的包含的一般形式如下: 

#include <filename.h>

        关于这两种包含形式的区别在于:

        1.包含头文件的形式不一样,一个使用 ”“ 另一个使用 <>。

        2.查找策略不同,<> 包含的头文件直接在标准路径查找,"" 包含的头文件现在源文件当前路径查找,然后在到标准路径去查找。也就是说 "" 的查找方式其实是包含了 <> 的查找方式,但是一般采取相对应的头文件引用格式,若都采用 "" 会使效率变差。

8.2 嵌套文件的包含

        在同一路径中的多源文件中,可能多个源文件都包含了同一个头文件,那么就相当于一个头文件在预处理阶段被多次导入到内存中,即浪费空间又浪费时间,对于这样的问题,我们同样可以使用条件编译来处理。

        若多个源文件中包含同一个头文件,就用如下的形式表示,因为最后都会链接成一个.exe文件。

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

        对应的解决方案如下(只需要加入相应的条件编译即可): 

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#endifint main()
{return 0;
}

        预编译阶段还有许多的知识,这里只列举出一些较为重要的内容。 

       

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

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

相关文章

Linux第29步_安装“Notepad++”软件

STM32CubeProgrammer脚本文件的后缀为“.tsv”&#xff0c;ST公司官方也叫做FlashLayout。在烧写“TF-A固件”之前&#xff0c;我们需要用“Notepad”软件打开“后缀为.tsv”的脚本文件&#xff0c;根据需求决定哪些文件需要更新&#xff0c;设置好这个脚本文件。 在后期使用S…

【Java SE语法篇】11.异常

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ 文章目录 1. 异常的概念和体系结构1.1 异常的概念1.2 异常体系…

【用队列实现栈】【用栈实现队列】Leetcode 232 225

【用队列实现栈】【用栈实现队列】Leetcode 232 225 队列的相关操作栈的相关操作用队列实现栈用栈实现队列 ---------------&#x1f388;&#x1f388;题目链接 用队列实现栈&#x1f388;&#x1f388;------------------- ---------------&#x1f388;&#x1f388;题目链…

信息技术安全评估准则新版标准的变化

文章目录 前言一、GB/T 18336 标准在我国的应用情况&#xff08;一&#xff09;以GB/T 18336 标准制定的信息技术产品国家标准&#xff08;二&#xff09;GB/T 18336 标准提升了国家关键信息基础设施的整体网络安全保障水平 二、新版 GB/T 18336 标准的变化及应用展望三、标准支…

AttributeError: module ‘openai‘ has no attribute ‘error‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

LaTeX系列3——插入图片

\documentclass[UTF-8]{ctexart} \usepackage{graphicx}\begin{document}在文档中插入图片\includegraphics[width0.5\linewidth]{flower}\end{document} 1.\usepackage{graphicx} 在插入图片之前要先声明我要使用graphicx包&#xff0c;但是我没有查到icx的含义&#xff0c;…

【UE5】交互式展厅数字博物馆交互是开发实战课程

长久以来&#xff0c;我们总是不断被初学者问到类似这样的问题&#xff1a;如何从头到尾做一个交互式程序开发项目&#xff1f;本套课程尝试对这个问题进行解答。 课程介绍视频如下 【UE5】数字展厅交互式开发全流程 【谁适合学习这门课】 本套课程面向初学者&#xff0c;满足…

java注释详解

1、Java 中的注释详解 概括&#xff1a;注释是增加一些说明&#xff0c;在编译后&#xff0c;注释会被抹掉&#xff0c;不起任何租用&#xff0c;只在书写代码的时候&#xff0c;对代码进行的一个说明 不管是那种编程语言&#xff0c; 代码的注释都是必备的语法功能&#xff…

Android PendingIntent 闪退

先来给大家推荐一个我日常会使用到的图片高清处理在线工具&#xff0c;主要是免费&#xff0c;直接白嫖 。 有时候我看到一张图片感觉很不错&#xff0c;但是图片清晰度不合我意&#xff0c;就想有没有什么工具可以处理让其更清晰&#xff0c; 网上随便搜下就能找到&#xff…

数据仓库(3)-模型建设

本文从以下9个内容&#xff0c;介绍数据参考模型建设相关内容。 1、OLTP VS OLAP OLTP&#xff1a;全称OnLine Transaction Processing&#xff0c;中文名联机事务处理系统&#xff0c;主要是执行基本日常的事务处理&#xff0c;比如数据库记录的增删查改,例如mysql、oracle…

视频SDK的技术架构优势和价值

为了满足企业对于高质量视频的需求&#xff0c;美摄科技推出了一款强大的视频SDK&#xff08;软件开发工具包&#xff09;&#xff0c;旨在帮助企业轻松实现高效、稳定的视频功能&#xff0c;提升用户体验&#xff0c;增强企业竞争力。 一、美摄视频SDK的技术实现方式 美摄视…

Ps:基于单个原色通道的抠图

基于单个原色通道的抠图&#xff0c;指的是&#xff1a;找出主体与背景反差最大的原色通道&#xff0c;然后将其复制为 Alpha 通道&#xff0c;并通过编辑此 Alpha 通道从而完善选区&#xff0c;是一种较简单的基于通道的抠图方法。 ◆ ◆ ◆ 找出反差最大的通道 在“通道”面…

高精度算法笔记

目录 加法 减法 乘法 除法 高精度加法的步骤&#xff1a; 1.高精度数字利用字符串读入 2.把字符串翻转存入两个整型数组A、B 3.从低位到高位&#xff0c;逐位求和&#xff0c;进位&#xff0c;存余 4.把数组C从高位到低位依次输出 1.2为准备 vector<int> A, B, C…

5文件操作

包含头文件<fstream> 操作文件三大类&#xff1a; ofstream : 写文件ifstream &#xff1a;读文件fstream : 读写文件 5.1文本文件 -文件以ascii的形式存储在计算机中 5.1.1写文件 步骤&#xff1a; 包含头文件 #include "fstream"创建流对象 ofs…

SQL进阶3

二、多表连结 1、什么叫联结 下面&#xff0c;我们举个例子来说明&#xff1a; 学校的安排的课程信息&#xff0c;我们平时都会为主要人员负责的对应课程信息创建表格&#xff0c;让其更好地检索得到对应数据信息。学生可以查到自己本身的课程信息&#xff0c;而老师也可以查…

HTML--表单

睡不着就看书之------------------------ 表单 作用&#xff1a;嗯~~动态页面需要借助表单实现 表单标签&#xff1a; 主要分五种&#xff1a; form&#xff0c;input&#xff0c;textarea&#xff0c;select&#xff0c;option 从外观来看&#xff0c;表单就包含以下几种&…

设计Twitter时间线和搜索功能

设计Twitter时间线和搜索功能 设计 facebook feed 和 设计 facebook search是相同的问题 第一步&#xff1a;定义用例和约束 定义问题的需求和范围&#xff0c;询问问题去声明用例和约束&#xff0c;讨论假设 ps: 没有一个面试官会展示详细的问题&#xff0c;我们需要定义一些用…

服务器推送数据你还在用 WebSocket么?

当涉及到推送数据时,人们首先会想到 WebSocket。 的确,WebSocket 允许双向通信,可以自然地用于服务器到浏览器的消息推送。 然而,如果只需要单向的消息推送,HTTP 通过服务器发送的事件也有这种功能。 WebSocket 的通信过程如下: 首先,通过 HTTP 切换协议。服务器返回 101 状…

U-Boot学习(4):u-boot.lds链接脚本分析

在之前的文章中有介绍U-Boot的编译流程&#xff0c;但我们知道&#xff0c;不同的存储介质可能会接在不同的接口上&#xff0c;如NOR Flash、EMMC和SDRAM等内存的接口是不同的&#xff0c;而不同的接口对应CPU就会映射到不同的内存中。所以如果我们需要运行U-Boot的话&#xff…

介绍下Redis?Redis有哪些数据类型?

一、Redis介绍 Redis全称&#xff08;Remote Dictionary Server&#xff09;本质上是一个Key-Value类型的内存数据库&#xff0c;整个数据库统统加载在内存当中进行操作&#xff0c;定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作&#xff0c;Redis的性…