【C语言进阶】预处理详解

引言

对预处理的相关知识进行详细的介绍

 158c3f50b199454985017a51dbef9841.png               ✨ 猪巴戒:个人主页✨

               所属专栏:《C语言进阶》

        🎈跟着猪巴戒,一起学习C语言🎈

目录

引言

预定义符号

#define定义常量

#define定义宏

带有副作用的宏参数

宏替换的规则

宏函数的对比

#和## 

#运算符

##运算符

命名约定

#undef

条件编译

头文件的包含

嵌套文件的包含


预处理又叫预编译,预处理是编译过程中的第一个步骤,主要是处理以#开头的预编译指令。

        

预定义符号

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

__FILE__    //进行编译的源文件的文件名
__LINE__    //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器崔寻ANSI C,其值为1,否则就是未定义。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __TIME__);printf("%s\n", __DATE__);return 0;
}

1c8f891dc5774057a6204866239de93a.png

         

#define定义常量

#define name stuff

在预编译的过程中,会用stuff来代替name。

比如下面的MAX就是要被替换的name,在预处理的阶段,MAX会被替换成100。

#define MAX 100
int main()
{printf("%d\n", MAX);return 0;
}

后面的stuff不仅可以是数字,也可以是一个长句。

#define PRINT printf("hello world!\n")
int main()
{PRINT;return 0;
}

65c157af11324fc2a81e2654eab31d37.png

        

#define定义宏

#define定义宏,不仅可以完成语句的替换,还可以将参数传进去。

#define name(parament_list) stuff

parament_list表示的就是参数,有了参数的加入,stuff的表达更加丰富。

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

比如说:

#define SQUARE(x) x+x
int main()
{int a = 10;printf("%d\n", SQUARE(a));return 0;
}

这里的SQUARE(x)中的 x 就是参数,将参数a代替x,然后带入后面的表达式。

这里的SQUARE(a),经过替换就是 a+a. 

043b70eb480d4d0b8234a1c670045981.png

但是这种定义宏的写法是存在瑕疵的,#define定义宏的书写一定要在各个参数上加上括号,在整体的表达式加上括号。

下面的书写方式才是正确的。

#define SQUARE(x) ((x)+(x))

接下来看两个错误的示范:

1.

#define SQUARE(x) x*x
int main()
{int a = 10;printf("%d\n", SQUARE(a+1));return 0;
}

这里的本来预想的结果是11*11=121,但是预处理并不是首先将参数进行计算,而是简单地将参数进行替换,SQUARE(a+1)会被替换成a+1*a+1(10+1*10+10),所以最后地结果是21。

解决办法就是

#define SQUARE(x) (x)*(x)

2.

#define SQUARE(x) x+x
int main()
{int a = 10;printf("%d\n", 10*SQUARE(a));return 0;
}

这里本来预想的结果是200,但是将参数进行替换,10*SQUARE(a)->10*a+a,结果就是110.

解决办法就是

#define SQUARE(x) (x+x)

总结:

为了避免宏定义中,参数中操作符或临近操作符之间不可预料的相互作用。我们应该毫不吝啬地在参数和参数表达式整体加上括号。

        

带有副作用的宏参数

刚刚讲了定义宏是什么,接下来是带有副作用的宏参数。如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

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

多次使用宏定义,那就会导致x的值发生变化,最终导致的结果就会大相径庭。

#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{int x = 4;int y = 5;int z = MAX(x++, y++);printf("%d %d %d\n", x, y, z);return 0;
}

fc84fbb0eb77484e91f728d35a671bd7.png

 这里x++,y++作为参数,MAX(x++,y++)会被替换成((x++)>(y++)?(x++):(y++)),首先x++和y++比较大小,x的值会变成5,y的值会变成6,那么后面的y++表达式就会被执行,因为是前置++,先执行后++,所以结果是6,但是y的值变成了7。

        

宏替换的规则

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

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

注意:

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

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

        

宏函数的对比

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现与一个地方,每次执行函数调用的都是同一个地方
执行速度更快存在函数的调用和返回函数栈帧的时间,速度会慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候直接将参数的值传递给函数。
带有副作用的参数参数可能被代替到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果函数参数只在传参的时候对参数进行求值
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用与任何参数类型函数的参数是与类型有关的,不同的参数类型就要不同的函数,所以比较严格
调试宏是不方便调试的函数是可以逐语句调试
递归宏是不能递归的 函数是可以递归的

         

#和## 

#运算符

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

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

#define PRINT(n) printf("the value of "#n"is %d",n)

之所以通过#运算符进行字符串化,是因为在字符串中,#define是不会进行替换的。

printf("the value of ""a""is %d",a);

##运算符

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

int int_max(int x, int y)
{return x > y ? x : y;
}float float_max(float x, float y)
{return x > y ? x : y;
}

由于类型的不同,这个比较大小的功能必须分为两个函数进行实现。

下面是一个定义宏,通过下面的定义宏将类型作为参数,替换函数中的类型。

其中\是换行符,type_max如果直接书写就是函数名称了,分开书写,然后用##来粘合,就可以实现替换函数名。

#define GENERIC_MAX(type)\
type type##_max(type x,type y)\
{\return (x>y?x:y);\
}GENERIC_MAX(int)
GENERIC_MAX(float)//等同于上面两个函数的代码

         

命名约定

一般来说函数和宏语法很相似,所以语言本身没法帮助我们区分二者。

那我们平时的一个习惯是

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

        

#undef

我们直到#define name就是定义一个常量,#undef就是取消宏定义。

#define MAX 100
#undef MAX     //这里往后MAX就取消宏定义,不能直接使用MAX了

        

条件编译

条件编译指令有

#ifdef #if #endif #elif #else #ifndef

#if 表达式 语句: 后面加表达式,和if的意思一样,但是这个步骤是在预处理的时候完成的,如果表达式为假,就不会出现在后面的文件中。

#elif 表达式 语句:和else if的意思一样,和#if搭配使用。

#else 语句:和else的意思一样。

#endif :不同的是我们在使用完条件编译指令后,要加上#endif,表示预处理完成。

#ifdef  :  意思:如果后面被定义,就执行后面的语句。

#ifndef : 意思:如果后面没被定义,就执行后面的语句。

int main()
{#ifdef MAXprintf("haha\n");#endifreturn 0;
}
#if 常量表达式//...
#endif
//常量表达式由预处理器求值。
如:#define __DEBUG__ 1
#if __DEBUG__//..
#endif2.多个分⽀的条件编译
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif3.判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol4.嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

         

头文件的包含

#incldue <filename>

库函数通过<>包含,会在指定的标准头文件位置查找头文件。

#include "filename"

通过”“包含,会先在源文件所在目录下查找,如果头文件未找到,编译器就会像查找库函数文件的方式一样去标准的头文件位置查找。

所以库函数也可以用”“来包含,但是这样查找的效率就慢,也不容易区分库文件和本地头文件。

        

嵌套文件的包含

#include指令会将头文件进行包含,在预处理阶段实现,但是如果是多个源文件的交互合作,那么程序会对同一个头文件多次包含,那么就会在大大增加程序包含的代码量。

所以我们通过条件编译解决头文件多次引用问题:

ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //或者
#pragma once

当头文件被第一次引用时,就会定义__TEST_H__,那么在第二次打开的时候,__TEST_H__就已经被定义,因为#ifndef,头文件就不会再次包含。

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

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

相关文章

理解LSTM一种递归神经网络(RNN)

1 递归神经网络结构 一个简单的传统神经网络结构如下图所示&#xff1a; 给他一些输入x0,x1,x2 … xt, 经过神经元作用之后得到一些对应的输出h0,h1,h2 … ht。每次的训练&#xff0c;神经元和神经元之间不需要传递任何信息。 递归神经网络和传统神经网络不同的一个点在于&am…

Linux 网络传输学习笔记

这篇是混合《Linux性能优化实战》以及 《Wireshark网络分析就这么简单》的一些关于Linux 网络的学习概念和知识点笔记 &#xff0c;主要记录网络传输流程以及对于TCP和UDP传输的一些影响因素 Linux 网络传输流程 借用一张倪朋飞先生的《Linux性能优化实战》课程中的图片 接收流…

利用STM32CubeMX和keil模拟器,3天入门FreeRTOS(2.1) —— 任务挂起和恢复

前言 &#xff08;1&#xff09;FreeRTOS是我一天过完的&#xff0c;由此回忆并且记录一下。个人认为&#xff0c;如果只是入门&#xff0c;利用STM32CubeMX是一个非常好的选择。学习完本系列课程之后&#xff0c;再去学习网上的一些其他课程也许会简单很多。 &#xff08;2&am…

Windows 下ffmpeg安装及实践

Windows 下ffmpeg安装及实践 背景安装实践其他 背景 最近负责音频文件处理相关的业务&#xff0c;涉及到 ffmpeg 对一些音频文件格式的校验&#xff0c;记录一下安装过程及踩坑过程。 安装 如图1所示&#xff0c;进入官网&#xff0c;在windows下任选一个文件&#xff1a;h…

day01 深度学习介绍

目录 1.1深度学习介绍 1.2神经网络NN 1、概念&#xff1a; 2、神经元 3、&#xff08;单层&#xff09;神经网络 4、感知机&#xff08;两层&#xff09; 5、多层神经网络 6、激活函数 &#xff08;1&#xff09;饱和与非饱和激活函数 &#xff08;2&#xff09;饱和激活…

写着玩的程序:pycharm实现无限弹窗程序(非病毒程序,仅整蛊使用)

运行环境 PyCharm 2023.2.1 python3.11 具体内容 源代码 import tkinter as tk from tkinter import messagebox import threadingclass PopupGenerator:def __init__(self):self.root tk.Tk()self.root.geometry("200x120")self.root.title("无限弹窗&qu…

LeetCode---380周赛

题目列表 3005. 最大频率元素计数 3006. 找出数组中的美丽下标 I 3007. 价值和小于等于 K 的最大数字 3008. 找出数组中的美丽下标 II 一、最大频率元素计数 这题就是个简单的计数题&#xff0c;正常遍历统计数据即可&#xff0c;关键是你要会写代码逻辑。 代码如下&…

Java设计模式-代理模式(7)

馆长准备了很多学习资料,其中包含java方面,jvm调优,spring / spring boot /spring cloud ,微服务,分布式,前端,js书籍资料,视频资料,以及各类常用软件工具,破解工具 等资源。请关注“IT技术馆”公众号,进行关注,馆长会每天更新资源和更新技术文章等。请大家多多关注…

steam游戏搬砖项目还能火多久?

最近放假回到老家&#xff0c;见了不少亲戚朋友&#xff0c;大家不约而同都在感叹今年大环境不好&#xff0c;工作不顺&#xff0c;生意效益不好&#xff0c;公司状况不佳&#xff0c;反问我们生意如何&#xff1f;为了让他们心里好受一点&#xff0c;我也假装附和道:也不咋地&…

为什么电脑降价了?

周末&#xff0c;非常意外地用不到3000元买到了一款2023年度发布的华为笔记本I5,16G,500G&#xff0c;基本是主流配置&#xff0c;我非常意外&#xff0c;看了又看&#xff0c;不是什么Hwawii&#xff0c;或者Huuawe。然后也不是二手。为什么呢&#xff1f;因为在ALU和FPU之外&…

Maven 打包时,依赖配置正确,但是类引入出现错误,一般是快照(Snapshot)依赖拉取策略问题

问题描述&#xff1a; 项目打包时&#xff0c;类缺少依赖&#xff0c;操作 pom.xml -> Maven -> Reload project &#xff0c;还是不生效&#xff0c;但是同事&#xff08;别人&#xff09;那里正常。 问题出现的环境&#xff1a; 可能项目是多模块项目&#xff0c;结构…

图论可达性c语言实现

概述 图论中的可达性是指在图中是否存在从一个顶点到另一个顶点的路径。这是图论中的一个基本概念&#xff0c;对于许多实际问题的建模和解决都非常重要。以下是关于图论可达性的一些重要概念和信息&#xff1a; 有向图和无向图&#xff1a; 图可以分为有向图和无向图。在有向图…

MySQL JSON数据类型

在日常开发中&#xff0c;我们经常会在 MySQL 中使用 JSON 字段&#xff0c;比如很多表中都有 extra 字段&#xff0c;用来记录一些特殊字段&#xff0c;通过这种方式不需要更改表结构&#xff0c;使用相对灵活。 目前对于 JSON 字段的实践各个项目不尽相同&#xff0c;MySQL 表…

未来趋势:视频美颜SDK与增强现实(AR)的融合

当下&#xff0c;视频美颜SDK不断演化&#xff0c;成为用户记录和分享生活时不可或缺的一部分。同时&#xff0c;增强现实技术也以其独特的沉浸感和交互性受到青睐&#xff0c;被广泛应用于游戏、教育、医疗等领域。 一、视频美颜与AR的结合 1.实时美颜的AR增值体验 借助AR的…

【Web前端开发基础】CSS的结构伪类选择器、伪元素、浮动

CSS的浮动 目录 CSS的浮动一、学习目标二、文章内容2.1 结构伪类选择器2.2 伪元素2.3 标准流2.4 浮动2.5 清除浮动2.6 拓展&#xff08;BFC&#xff09; 三、综合案例3.1 小米模块案例3.2 网页导航案例 一、学习目标 能够使用结构伪类选择器在HTML中选元素能够说出标准流元素的…

服务器管理平台(5)- 数据展示

数据展示 Grafana导入MySQL数据源进行定制化数据展示&#xff0c;包括品牌分类饼图&#xff0c;详细数据列表等LayUI为开源前端框架&#xff0c;对系统概览、登录日志等信息&#xff0c;划分不同页面使用表格展示详细数据 1、Grafana 对品牌、CPU型号、内存等数据使用饼图展示…

探索全球DNS体系 | 从根服务器到本地解析

DNS 发展 DNS&#xff08;Domain Name System&#xff09;的起源可以追溯到互联网早期。 早期的挑战&#xff1a; 早期互联网主要通过IP地址进行通信&#xff0c;用户需要记住复杂的数字串来访问网站。 需求的催生&#xff1a; 随着互联网的扩大&#xff0c;更简单、易记的…

【剑指offer】重建二叉树

&#x1f451;专栏内容&#xff1a;力扣刷题⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、题目描述1、题目2、示例 二、题目分析1、递归2、栈 一、题目描述 1、题目 剑指offer&#xff1a;重建二叉树 给定节…

在CentOS 7中配置 RAID服务

实验过程 Xnode1克隆虚拟机raid ps&#xff1a; 阿里云盘Xnode1获取 xnode1 https://www.alipan.com/s/HgLXfoeBWG2 提取码: eb70 编辑虚拟机 添加2硬盘 CRT连接&#xff08;root密码&#xff1a;000000&#xff09; 创建raid 0 [rootdemo ~]# lsblk 安装mdadm [rootdemo…

go语言(十四)----反射

变量的结构 2 举个例子 package mainimport "fmt"type Reader interface {ReadBook() }type Writer interface {WriteBook() }//具体类型 type Book struct {}func (this *Book) ReadBook() {fmt.Println("Read a Book")}func (this *Book) WriteBook() {…