【C语言基础】:预处理详解(一)

news2025/6/8 9:22:33

文章目录

      • 一、预定义符号
      • 二、#define定义常量
      • 三、#define定义宏
      • 四、带有副作用的宏参数
      • 五、宏替换的规则

一、预定义符号

在C语言中设置了许多的预定义符号,这些预定义符号是可以直接使用的,预定义符号也是在预处理阶段进行处理的。

常见的预定义符号

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

【示例】

#include<stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%d\n", __LINE__);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
我们在VS上使用 _ _ STDC _ _ 会发现显示未定义,这也就说明VS的编译器是不完全遵循ANSI C的,为了展示效果,我没可以在gcc的环境下查看一下。
在这里插入图片描述
在gcc环境下运行可以看到它输出的是1,这表明gcc环境下的编译器是遵循ANSI C的。
预处理之后我们会发现,前面我们就学过,程序在预处理之后会把预定义指令给替换掉,这里结果也确实如此。
在这里插入图片描述

二、#define定义常量

#define一般有两种应用场景:

  1. #define定义常量
  2. #define定义宏

#define定义常量基本语法:

#define name stuff

【示例】

#include<stdio.h>
#define MAX 100
#define STR "hello world"
int main()
{
	int a = MAX;
	printf("%d\n", MAX);
	printf("%s\n", STR);
	return 0;
}

在这里插入图片描述

#define定义标识符加不加 的区别:

#include<stdio.h>
#define MAX 100
#define MAX1 100;
int main()
{
	int a = MAX;
	int b = MAX1;
	printf("%d\n", MAX);
	printf("%d\n", MAX1);
	return 0;
}

在这里插入图片描述
可以看到,MAX1加了分号之后, 之后后面使用的MAX1全都加上了分号,这也就导致了在打印MAX1时报错,在预处理之后可以清楚的看到原因(#define把;也替换过来了)。所以一般使用 #define 定义常量时,不要加分号。

三、#define定义宏

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

宏的申明方式

#define name( parament-list ) stuff

parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的⼀部分。

【示例】:利用宏求一个数的平方

#include<stdio.h>
#define SQURE(x) x * x
int main()
{
	int a = 12;
	int b = SQURE(a);
	printf("%d\n", b);
	return 0;
}

在这里插入图片描述
因为参数是允许替换到文本中的,把a传给SQURE(a)就相当于把程序替换成了a * a, 这里预处理后也能看到效果。

但是这个宏也存在着一些问题:

int a = 5;
printf("%d\n", SQURE(a + 1));

按照惯性,我们会觉得这个代码的运行结果会是6 * 6 = 36,但结果真的会是这样吗?
我们来运行试一下:
在这里插入图片描述
运行之后可以发现结果等于11,这里就要注意了,宏的参数是不会参与计算的,会直接进行替换,我们进行预处理生成目标文件后可以发现SQURE(a + 1)替换成了a + 1 * a + 1,而 * 的优先级比 + 高,导致会先算 * 再算 + ,a等于5,乘以一还是5,再加上6就等于11。

那怎么让他得到36呢,其实这里加个括号就可以了。

#include<stdio.h>
#define SQURE(x) (x) * (x)
int main()
{
	int a = 5;
	int b = SQURE(a + 1);
	printf("%d\n", b);
	return 0;
}

在这里插入图片描述
当然,下面这种方法也是一样的。
在这里插入图片描述
我们只要确保替换之后运算顺序不发生改变就可以达到目的了。

下面是一个宏定义:

#define DOUBLE(x) (x) + (x)

定义中我们使用了括号,虽然这样可以避免之前的问题,但是这个宏定义可能会出现新的问题:

#include<stdio.h>
#define DOUBLE(x) (x) + (x)
int main()
{
    int a = 5;
    printf("%d\n", 5 * DOUBLE(a));
    return 0;
}

按照惯性思维,我们可能会认为打印50,但结果是否会是50呢?
在这里插入图片描述
结果发现打印的是30,预处理之后生成目标文件之后可以发现5会先和a相乘,然后再加a,导致结果与我们的出现误差。
这个问题,的解决办法是在宏定义表达式两边加上⼀对括号就可以了。

#define DOUBLE(x) ((x) + (x))

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

四、带有副作用的宏参数

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

【示例】

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

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

#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main()
{
    int a = 3;
    int b = 5;
    int m = MAX(a++, b++);
    printf("m = %d\n", m);
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    return 0;
}

在这里插入图片描述
替换之后是一个三目运算符,首先a = 3,b = 5,由于是后置加加,判断之后才会加一,所以判断之后a就等于4,b就等于6,因为表达式为假,后面那个a++不会执行,a还是等于4,后面的b++会执行,但由于也是后置加加,先使用后再加一,即m就等于6,b就等于7。

结论:如果一个带有副作用的参数在宏定义中出现两份,就有可能出现不同的结果,即带有副作用的参数是非常危险的,要尽量避免使用。

五、宏替换的规则

宏替换是C语言预处理器的一个重要功能,它在编译之前进行文本替换。宏替换遵循一定的规则,这些规则确保了宏能够在正确的上下文中被替换为定义的文本,需要涉及几个步骤:

  1. 文本替换
    宏定义中的所有文本都将被直接替换到宏名出现的任何位置。这意味着宏名在代码中出现的每个地方,都会用宏定义中的文本替换。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
    int a = 3;
    int b = 5;
    int m = MAX(a, M);
    printf("m = %d\n", m);
    printf("M = %d\n", M);
    printf("b = %d\n", b);
    return 0;
}

在这里插入图片描述
每个宏名的位置都会用宏定义中的文本替换。

  1. 宏参数的保留
    当宏名被替换时,宏参数将保持其原始的括号结构。这是为了避免改变操作符的优先级和结合性,确保代码的逻辑不变。

  2. 宏参数的展开
    宏参数在替换时会展开,这意味着如果宏参数本身是一个宏,它也会被展开(替换)。这个过程称为宏的展开或宏的宏展开。
    在这里插入图片描述

  3. 宏展开的顺序
    当宏参数中包含其他宏时,预处理器会按照它们在宏定义中出现的顺序进行替换。如果宏A中使用了宏B,而宏B又使用了宏C,那么预处理器首先会替换宏C,然后是宏B,最后是宏A。

  4. 宏展开的深度
    宏展开的深度是有限的。如果一个宏展开后仍然是一个宏(即宏的宏),这个过程会继续,但是有一个深度限制,以避免无限循环。

  5. 宏定义的顺序
    宏定义的顺序可能会影响宏替换的结果。如果两个宏相互依赖,可能会导致预处理错误。为了解决这个问题,可以使用宏的函数样宏形式,或者确保依赖关系正确。

  6. 宏定义的优先级
    如果两个宏定义具有相同的名称,预处理器将使用最后一个定义。这意味着宏定义可以被后续的宏定义覆盖。

  7. 条件编译中的宏替换
    在使用#ifdef、#ifndef、#if、#else、#elif和#endif等条件编译指令时,只有当条件为真时,相关的宏才会被替换。

  8. 字符串化和标记粘贴
    预处理器提供了特殊的宏操作符,如字符串化运算符#和标记粘贴运算符##。字符串化运算符可以将宏参数转换为字符串字面量,而标记粘贴运算符可以将两个宏参数连接成一个单一的标识符。

  9. 宏展开的最佳实践
    为了避免宏展开引起的问题,建议使用括号包围宏参数,避免宏定义过于复杂,以及避免宏名与关键字或现有标识符冲突。

注意

  1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
#include<stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define M 10
int main()
{
    int a = 3;
    int b = 5;
    int m = MAX(a, MAX(2, 3));
    printf("m = %d\n", m);
    printf("M = %d\n", M);
    printf("b = %d\n", b);
    return 0;
}

在这里插入图片描述

  1. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索。
    在这里插入图片描述

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

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

相关文章

uniapp开发小程序手写板、签名、签字

可以使用这个插件进行操作 手写板-签名签字-lime-signature - DCloud 插件市场 但是目前这个插件没有vue3 setup Composition API的写法。所以对于此文档提供的可以直接使用,需要使用Composition API方式实现的,可以继续看。 因为Composition API方式,更加的简单、灵活,…

逆向案例二十三——某租逆向,总是有映射源文件怎么办以及分析webpack代码

网址&#xff1a;aHR0cHM6Ly93d3cubWFvbWFvenUuY29tLyMvYnVpbGQ 抓取数据包发现载荷以及数据都进行了加密&#xff1a; 定位方法一&#xff1a;直接搜decrypt(,进入js文件&#xff0c;可以发现就是直接AES的解密方法&#xff0c;打上断点&#xff0c; 下方的d是解密函数 现在有…

vscode配置c\c++及美化

文章目录 vscode配置c\c及美化1.安装vscode2.汉化3.安装c\c插件4.安装mingw5.配置mingw6. 运行c代码6.1 创建代码目录6.2 设置文件配置6.3 创建可执行任务&#xff1a;task.json6.4 编译执行6.5 再写其他代码6.6 运行多个c文件 7. 运行c文件8.调式代码8.1 创建launch.json8.2 修…

010、Python+fastapi,第一个后台管理项目走向第10步:ubutun 20.04下安装ngnix+mysql8+redis5环境

一、说明 先吐槽一下&#xff0c;ubuntu 界面还是不习惯&#xff0c;而且用的是云电脑&#xff0c;有些快捷键不好用&#xff0c;只能将就&#xff0c;谁叫我们穷呢&#xff1f; 正在思考怎么往后进行&#xff0c;突然发现没安装mysql 和redis&#xff0c;准备安装&#xff0…

shell 调用钉钉通知

使用场景&#xff1a;机器能访问互联网&#xff0c;运行时间任务后通知使用 钉钉建立单人群 手机操作&#xff0c;只能通过手机方式建立单人群 电脑端 2. 配置脚本 #!/bin/bash set -e## 上图中 access_token字段 TOKEN KEYWORDhello # 前文中设置的关键字 function call_…

Visual Studio code无法正常执行Executing task: pnpm run docs:dev

最近尝试调试一个开源的项目&#xff0c;发现cmd可以正常启动&#xff0c;但是在vs中会报错&#xff0c;报错内容如下 Executing task: pnpm run docs:dev pnpm : 无法加载文件 E:\XXXX\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 http…

数据结构之单链表相关刷题

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构 数据结构之单链表的相关知识点及应用-CSDN博客 下面题目基于上面这篇文章&#xff1a; 下面有任何不懂的地方欢迎在评论区留言或…

【重回王座】ChatGPT发布最新模型gpt-4-turbo-2024-04-09

今天&#xff0c;新版GPT-4 Turbo再次在大型模型排行榜上荣登榜首&#xff0c;成功超越了此前领先的Claude 3 Opus。另外&#xff0c;新模型在处理长达64k的上下文时&#xff0c;性能竟能够与旧版在处理26k上下文时的表现相当。 目前GPT-4 Turbo仅限于ChatGPT Plus的用户&…

嵌入式sqlite3交叉编译移植

操作系统:Ubuntu20.04 下载sqlite3代码,下载版本3.30.00 wget https://www.sqlite.org/2019/sqlite-amalgamation-3300000.zip 或者https://download.csdn.net/download/benico/89127678 为什么下载amalgamation版本,不下载autoconf版本? 根据我的编译实验,同版本sql…

C++设计模式:代理模式(十三)

1、代理模式 定义&#xff1a;为其他对象提供一种代理以控制&#xff08;隔离使用接口&#xff09;对这个对象的访问等。 动机 在面向对象系统中&#xff0c;有些对象由于某种原因&#xff08;比如对象需要进程外的访问等&#xff0c;例如在分布式的系统中&#xff09;&#x…

基于Docker构建CI/CD工具链(六)使用Apifox进行自动化测试

添加测试接口 在Spring Boot Demo项目里实现一个简单的用户管理系统的后端功能。具体需求如下&#xff1a; 实现了一个RESTful API&#xff0c;提供了以下两个接口 &#xff1a; POST请求 /users&#xff1a;用于创建新的用户。GET请求 /users&#xff1a;用于获取所有用户的列…

【日常记录】【CSS】利用动画延迟实现复杂动画

文章目录 1、介绍2、原理3、代码4、参考链接 1、介绍 对于这个效果而言&#xff0c;最先想到的就是 监听滑块的input事件来做一些操作 ,但是会发现&#xff0c;对于某一个节点的时候&#xff0c;这个样式操作起来比较麻烦 只看这个代码的话&#xff0c;发现他用的是动画&#x…

什么是T型槽铸铁平板中内应力——河北北重厂家

T型槽铸铁平板中的内应力指的是平板内部受到的内部力&#xff0c;包括拉应力和剪应力。在T型槽铸铁平板使用过程中&#xff0c;由于自身重量、外力加载等原因&#xff0c;会产生内部应力。这些内应力是平板内部各部分之间的相互作用力&#xff0c;使得平板各部分受到不同的拉伸…

部署HDFS集群(完全分布式模式、hadoop用户控制集群、hadoop-3.3.4+安装包)

目录 前置 一、上传&解压 &#xff08;一 &#xff09;上传 &#xff08;二&#xff09;解压 二、修改配置文件 &#xff08;一&#xff09;配置workers文件 &#xff08;二&#xff09;配置hadoop-env.sh文件 &#xff08;三&#xff09;配置core-site.xml文件 &…

【深度学习】图像风格混合——StyleGAN2原理解析

1、前言 上一篇文章&#xff0c;我们详细讲解了StyleGAN的原理。这篇文章&#xff0c;我们就来讲解一下StyleGAN2&#xff0c;也就是StyleGAN的改进版。 原论文&#xff1a;Analyzing and Improving the Image Quality of StyleGAN 参考代码&#xff1a;①Pytorch版本&#…

腐蚀Rust 服务端搭建架设个人社区服务器Windows教程

腐蚀Rust 服务端搭建架设个人社区服务器Windows教程 大家好我是艾西&#xff0c;一个做服务器租用的网络架构师也是游戏热爱者。最近在steam发现rust腐蚀自建的服务器以及玩家还是非常多的&#xff0c;那么作为服务器供应商对这商机肯定是不会放过的哈哈哈&#xff01; 艾西这…

wangeditor与deaftjs的停止维护,2024编辑器该如何做技术选型(一)

wangeditor暂停维护的声明&#xff1a; wangeditor是国内开发者开发的编辑器&#xff0c;用户也挺多&#xff0c;但是由于作者时间关系&#xff0c;暂停维护。 deaft的弃坑的声明&#xff1a; draft是Facebook开源的&#xff0c;但是也弃坑了&#xff0c;说明设计的时候存在很大…

第二证券策略:股指预计维持震荡格局 关注汽车、工程机械等板块

第二证券指出&#xff0c;指数自今年2月份阶段低点反弹以来&#xff0c;3月份持续高位整理。进入4月份之后面对年报和一季报的双重财报发表期&#xff0c;预计指数短期保持高位整理概率比较大。前期缺乏成绩支撑的概念股或有回落的危险&#xff0c;主张重视成绩稳定、估值低、分…

Redis队列与Stream

Redis队列与Stream、Redis 6多线程详解 Redis队列与StreamStream总述常用操作命令生产端消费端单消费者消费组消息消费 Redis队列几种实现的总结基于List的 LPUSHBRPOP 的实现基于Sorted-Set的实现PUB/SUB&#xff0c;订阅/发布模式基于Stream类型的实现与Java的集成消息队列问…

Tuxera Ntfs for mac 2023中文解锁版安装、密钥下载与激活教程 Tuxera激活码 tuxera破解

Tuxera Ntfs for mac2023是Mac中专用于读写外置存储的工具&#xff0c;具有强大的磁盘管理和修复功能&#xff0c;它在Mac上完全读写NTFS格式硬盘&#xff0c;快捷的访问、编辑、存储和传输文件。能够在 Mac 上读写 Windows NTFS 文件系统。Tuxera NTFS 实现在Mac OS X系统读写…