预处理详解

目录

预定义符号介绍

​编辑

预处理指令 #define

#define 定义标识符

#define 定义宏

#define 替换规则

#define中#和##的使用

带副作用的宏参数

宏和函数的对比

命令行定义

预处理指令 #undef

预处理指令 #include

头文件被包含的方式:

本地文件包含

库文件包含

预处理指令  条件编译

1.常量条件编译

 2.多个分支的条件编译

 3.判断符号是否被定义

 4.嵌套使用条件编译


预定义符号介绍

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
__FUNCTION__//当前代码所在函数

这些符号都是内置,可以通过这些符号记录一些代码信息或者说是日志信息,可以根据这些信息分析代码哪里出现问题

预处理指令 #define

#define 定义标识符

原型:
define name stuff

举例:

define MAX 100
如果在写代码的过程中使用了该符号,它会在预编译的阶段把该符号替换成相对应的stuff部分, 简单来说就是在代码中使用MAX,他会在预编译阶段把它替换成100

define不止可以定义常量,还可以定义类性相当于重新起个名字,甚至可以定义语句
#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,       \__DATE__,__TIME__ )

其实就是把标识符后面的内容在预编译阶段全部替换上去,可以减少一定代码冗余,以及方便修改

可以加 ‘;’ 但是会把  ‘;’ 一起替换上去,可能会影响原代码,例如:

#define M 100;
int main()
{
int a = M;
//预编译之后就会变成
int a = 100;;
}

define定义不是语句不用加 ‘;’它的单位是行,如果嫌写在一行太长就可以用斜杠续行,方便自己与他人阅读

#define 定义宏

原型:
#define name( parament-list ) stuff

举例:

#define SQUARE( x )  x * x

name后面不能跟空格,要不然它会被认为是定义标识符,从而把后面的内容理解为stuff与定义标识符不同宏是有参数的,它可以分为两个部分替换,一个是参数部分替换,一个是除参数部分替换

这里需要注意的是宏与函数不同,宏本质是替换,如果我们在参数部分传进去一个表达式,这个表达式不会算出一个结果,而是直接放进去,可能会因为操作符的优先级不同而导致结果不是预期所想,例如:
#define SQUARE( X )  X * X
int main()
{
int a = SQUARE(3+1);//预期结果16
return 0;
}
//预编译之后
#define SQUARE( X )  X * X
int main()
{
int a = 3+1*3+1;//7
return 0;
}

要避免这种问题,本质上是要在替换时避免歧义,要想避免歧义就应该把宏定义的内容分离出来,就是加上括号,而宏内部又有参数的替换,所以又要避免参数替换与除参数部分替换(这里就称宏的框架吧)发生歧义,所以参数部分又要从宏的框架中分离出来。例如:

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

#define 替换规则

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

#define中#和##的使用

#:

当我们在定义宏时想在字符串中使用参数,就可以使用#,它可以把参数转成字符串,此操作可以做到一些字符串做不到的事情,例如:我想实现一个根据不同参数输出不同字符串的功能

使用用函数:

#include<stdio.h>
void My_Print(char name) 
{printf("我是%c\n", name);
}
int main() 
{char a = 'a';My_Print(a);char b = 'b';My_Print(b);char c = 'c';My_Print(c);char d = 'd';My_Print(d);}

使用宏:

#include<stdio.h>
#define PRINT(X) printf("我是"#X"\n");
int main()
{PRINT(a)//相当于将a替换成"a",就变成printf("我是""a""\n");PRINT(b)PRINT(c)PRINT(d)}

我们发现使用宏就不用大费周章去定义变量和函数,直接传文本进去就能使用

##:

可以把两个传进来的文本合成为一个文本

例如:

#include<stdio.h>
#define UNION(X,Y) X##Y;
int main()
{int a = UNION(12, 3)printf("%d", a);//输出123
}

带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
x+1;//不带副作用
x++;//带有副作用
MAX 宏可以证明具有副作用的参数所引起的问题。
#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

宏和函数的对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#include<stdio.h>
#define MAX(a, b) ((a)>(b)?(a):(b))
int Max(int a, int b) 
{return a > b ? a : b;
}
int main() 
{int a = 10;int b = 20;int ret=Max(a,b);printf("%d %d", ret, MAX(a, b));return 0;}

那为什么不用函数来完成这个任务?

原因有二:
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比 函数在程序的规模和速度方面更胜一筹 。我们进入反汇编看一下比较一下就清楚了
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于> 来比较的类型。 宏是类型无关的

当然和宏相比函数也有劣势的地方:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的,因为宏是在预编译阶段就完成替换,而调试是在程序运行时调试,也就是已经变成可执行文件,所以在调试阶段就看不见宏做的那些工作。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型 ,但是函数做不到。

总结:当需要实现一个简单的功能使用宏好,复杂就用函数好

命令行定义

在命令行时定义,就是预处理指令不在代码内部,而是用于启动编译过程添加,也就是预编译之前,简单来说就是不会出现在源文件。

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)
#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 programe.c

预处理指令 #undef

用于取消define定义

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

预处理指令 #include

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方 一样。 这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。
这样一个源文件被包含 10 次,那就实际被编译 10 次。

头文件被包含的方式:

本地文件包含

#include "filename.h"

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

库文件包含

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的, 可以

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

#include <filename.h>
避免嵌套文件包含
comm.h comm.c 是公共模块。
test1.h test1.c 使用了公共模块。
test2.h test2.c 使用了公共模块。
test.h test.c 使用了 test1 模块和 test2 模块。
这样最终程序中就会出现两份 comm.h 的内容。这样就造成了文件内容的重复
如何解决这个问题呢?加上一行代码就行,就可以避免头文件的重复引入。
#pragma once

例如:

#include<stdio.h>
#define MAX(a, b) ((a)>(b)?(a):(b))
#undef MAX
int main()
{int a = 10;int b = 20;printf("%d ", MAX(a, b));return 0;}

预处理指令  条件编译

条件满足就编译,不满足被条件编译框住的部分就不让编译,用法与if语句类似,但是if存在于可执行文件中,也就是说程序无论走不走if都会在可执行文件中,而条件编译不同,条件满足就编译,不满足在预编译阶段就删除了,它不会存在于预编译之后文件。

1.常量条件编译

//格式
#if 常量表达式
//...
#endif//例如:------------------------------------
int main() 
{
#define __DEBUG__ 0
#define __RELEASE__ 1
#if __DEBUG__<__RELEASE__//常量表达式由预处理器求值。printf("hello world");
#endif
}

 2.多个分支的条件编译

//格式
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif//例如-----------------------------------------
#define VERSION 2#if VERSION == 1#define MESSAGE "Version 1.0"
#elif VERSION == 2#define MESSAGE "Version 2.0"
#else#define MESSAGE "Unknown version"
#endif#include <stdio.h>int main() {printf("%s\n", MESSAGE);return 0;
}

 3.判断符号是否被定义

//格式--------------------------------------------------
//两种不同的写法和两种不同的操作
//如果符号存在
#if defined(symbol)
//...
#endif#ifdef symbol
//...
#endif//如果符号不存在
#if !defined(symbol)
//...
#endif#ifndef symbol
//...
#endif//例如:-----------------------------------------------------
//如果符号存在
#include<stdio.h>
#define DEBUG_MODE
int main() {
#if defined(DEBUG_MODE)printf("This is a debug message.\n");
#endif#ifdef DEBUG_MODEprintf("This is a debug message.");
#endifreturn 0;
}//如果符号不存在
int main() {
#if !defined(DEBUG_MODE)printf("N0 debug message.\n");
#endif#ifndef DEBUG_MODEprintf("No debug message.");
#endifreturn 0;
}

 
4.嵌套使用条件编译

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

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

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

相关文章

【Unity】【VR开发】Unity云同步功能使用心得

【背景】 有时出差,旅行等等也带着电脑,晚上想要继续编辑项目,就需要用到云同步功能。目前实践下来,发现有些内容可以同步,有些内容则是不可以同步的,总结如下。 【如何云同步一个本地项目】 UnityHub的项目面板中有两个选项卡:项目和云端项目。 鼠标挪动到想要云同步…

c++类和对象新手保姆级上手教学(中)

前言&#xff1a; 类和对象中篇&#xff0c;这里讲到的前4个默认成员函数&#xff0c;是类和对象中的重难点&#xff0c;许多资料上的讲法都非常抽象&#xff0c;难以理解&#xff0c;所以我作出这篇总结&#xff0c;分享学习经验&#xff0c;以便日后复习。 目录 6个默认成员…

土壤墒情监测站的工作原理

TH-TS600土壤墒情自动监测站是一种用于自动检测土壤墒情的仪器&#xff0c;它可以实时监测土壤的水分含量和温度&#xff0c;并将数据传输到数据中心或监测中心进行分析和处理。 土壤墒情自动监测站通常由传感器、数据采集器、数据传输设备和数据处理软件等部分组成。传感器是…

Laravel02 路由基本概念和用法 给视图传递请求参数

Laravel02 路由基本概念和用法 1. 路由的基本概念2. 给视图传递请求参数 1. 路由的基本概念 routes文件夹下的web.php是用来定义路由规则的。 自己定义一个路径 2. 给视图传递请求参数 在laravel里使用一个辅助函数request来快速获取请求参数

NX/UG二次开发—CAM—平面铣边界准确设置方法

大家在对平面铣设置边界时&#xff0c;经常遇到边界方向与自己期望的不一致&#xff0c;有些人喜欢用检查刀路是否过切来判断&#xff0c;但是对于倒角、负余量等一些情况&#xff0c;刀路本来就是过切的。对于多边界&#xff0c;可以根据选择的曲线来起点和面的方向来确定&…

Camera2 createCaptureSession源码分析

当应用调用CameraManager#openCamera获取到已打开的camera设备后&#xff0c;会调用createCaptureSession方法来完成camera stream创建和stream的相关配置。在createCaptureSession方法中&#xff0c;首先将应用的surfaces信息封装成可跨binder传递的OutputConfiguration对象&a…

ACE 中的Active Object模式

Active Object 设计模式&#xff1a; 1&#xff09; 根据对象被调用的方式&#xff0c;可以将对象分为两类: Passive Object和Active Object。Passive 和 Object和调用者在同一个线程中&#xff0c;这就是我们通常所用的函数调用。而Active Object和调用在不同的线程中&#xf…

Leo赠书活动-16期 名校毕业生教材

Leo赠书活动-16期 名校毕业生教材 ✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠…

大厂的数据质量中心系统设计

日常工作中&#xff0c;数据开发上线完一个任务后并不是就可以高枕无忧&#xff0c;时常因上游链路数据异常或者自身处理逻辑的 BUG 导致产出的数据结果不可信。而问题发现可经历较长周期&#xff08;尤其离线场景&#xff09;&#xff0c;往往是业务方通过上层数据报表发现数据…

华为---RSTP(二)---RSTP基本配置示例

目录 1. 示例要求 2. 网络拓扑图 3. 配置命令 4. 测试终端连通性 5. RSTP基本配置 5.1 启用STP 5.2 修改生成树协议模式为RSTP 5.3 配置根交换机和次根交换机 5.4 设置边缘端口 6. 指定端口切换为备份端口 7. 测试验证网络 1. 示例要求 为防止网络出现环路&#xf…

【论文精读】MAE

摘要 将掩码重建任务从nlp引入到cv&#xff0c;提出非对称掩码自编码器。 框架 概述 如上图&#xff0c;本文提出掩码自编码器&#xff0c;即将给定原始信号的部分观测值的情况下重建原始信号&#xff0c;编码器将观察到的部分信号(没有掩码标记)映射到潜在表示&#xff0c;采…

Golang for 循环

从基础知识到高级技术、并发和通道 Go&#xff08;Golang&#xff09;编程语言中的“for”循环是一个基本而多功能的结构&#xff0c;用于迭代集合、重复执行代码块以及管理循环控制流。Golang的“for”循环语法简洁却强大&#xff0c;为处理多样的循环场景提供了一系列能力。无…

算法——数值算法——牛顿迭代法

目录 牛顿迭代法 一、1021: [编程入门]迭代法求平方根 牛顿迭代法 迭代法&#xff08;Iteration&#xff09;是一种通过反复递推计算来逼近解的方法。而牛顿迭代法&#xff08;Newtons method&#xff09;则是一种特定的迭代法&#xff0c;用于求解方程或函数的根、最小值、最…

MySQL数据库基础(十):DQL数据查询语言

文章目录 DQL数据查询语言 一、数据集准备 二、select查询 三、简单查询 四、条件查询 1、比较查询 2、范围查询 3、逻辑查询 4、模糊查询 5、非空查询 五、排序查询 六、聚合查询 七、分组查询与having子句 1、分组查询介绍 2、group by的使用 3、group by 聚…

【设计模式】23种设计模式笔记

设计模式分类 模板方法模式 核心就是设计一个部分抽象类。 这个类具有少量具体的方法&#xff0c;和大量抽象的方法&#xff0c;具体的方法是为外界提供服务的点&#xff0c;具体方法中定义了抽象方法的执行序列 装饰器模式 现在有一个对象A&#xff0c;希望A的a方法被修饰 …

单片机学习笔记---红外遥控红外遥控电机调速(完结篇)

目录 低电平触发中断和下降沿触发中断的区别 红外遥控 Int0.c Int.h Timer0.c Timer0.h IR.c IR.h main.c 红外遥控电机调速 Timer1.c Timer.h Motor.c Motor.h main.c 上一节讲了红外发送和接收的工作原理&#xff0c;这一节开始代码演示&#xff01; 提前说…

微信小程序-表单提交和校验

一、使用vant组件生成如下页面 二、前端代码如下 <form bindsubmit"submitForm"><view class"cell-group"><van-cell-group><van-field value"{{ title }}" label"商品名称" placeholder"请输入商品名称&qu…

Dubbo框架admin搭建

Dubbo服务监控平台&#xff0c;dubbo-admin是图形化的服务管理界面&#xff0c;从服务注册中心获取所有的提供者和消费者的配置。 dubbo-admin是前后端分离的项目&#xff0c;前端使用Vue&#xff0c;后端使用springboot。因此&#xff0c;前端需要nodejs环境&#xff0c;后端需…

复高斯分布的随机变量的模方的分布

文章目录 复高斯分布的随机变量的模方的分布问题的源头矩阵服从复高斯分布向量服从复高斯分布 复高斯分布的随机变量的模方的分布 已知 X ∼ C N ( μ , Σ ) X \sim \mathcal{C N}(\boldsymbol{\mu}, \boldsymbol{\Sigma}) X∼CN(μ,Σ) 则 ∥ X ∥ 2 \|X\|^2 ∥X∥2的分布为…

如何在本地服务器部署TeslaMate并远程查看特斯拉汽车数据无需公网ip

文章目录 1. Docker部署TeslaMate2. 本地访问TeslaMate3. Linux安装Cpolar4. 配置TeslaMate公网地址5. 远程访问TeslaMate6. 固定TeslaMate公网地址7. 固定地址访问TeslaMate TeslaMate是一个开源软件&#xff0c;可以通过连接特斯拉账号&#xff0c;记录行驶历史&#xff0c;统…