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

文章目录

      • 一、预定义符号
      • 二、#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 定义的符号的时候,字符串常量的内容并不被搜索。
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【系统分析师】计算机组成与体系架构

文章目录 1、编码及浮点数运算2、flynn分类法3、CISC和 RISC4、流水线技术5、存储技术5.1层次化存储结构5.2 Cache5.2.1 cache页面淘汰5.2.2 cache的读写 5.3 主存5.3.1主存编址5.3.2 磁盘 5.4 总线 6、校验码7、系统可靠性计算7.1可靠性指标7.2 串联系统与并联系统7.3性能指标…

Vue3——html-doc-ja(html导出为word的js库)

一、下载 官方地址 html-doc-js - npm npm install html-doc-js 二、使用方法 // 使用页面中引入 import exportWord from html-doc-js// 配置项以及实现下载方法 const wrap document.getElementById(test)const config {document:document, //默认当前文档的document…

如何将三方库集成到hap包中——通过IDE集成非cmake方式构建的C/C++三方库

简介 DevEco Studio(简称IDE)目前只支持cmake构建方式&#xff0c;对于非cmake构建方式的三方库需要通过IDE工具集成的话&#xff0c;我们需要对原生库编写一个cmake的构建脚本。本文通过tinyxpath三方库为例介绍如何在IDE上移植一个非cmake构建方式的三方库。 cmake构建脚本…

酷得智能 无人机方案开发

东莞市酷得智能科技有限公司&#xff0c;是一家专业的技术服务公司&#xff0c;致力于为各类智能硬件提供高效、稳定、安全的底层驱动解决方案。拥有一支经验丰富、技术精湛的团队&#xff0c;能够为客户提供全方位的底层驱动开发服务。 无人机功能介绍&#xff1a; 1、自动跟…

字符和字符串操作函数总结

索引 一 . 字符操作函数1. 字符分类函数2. 字符转换函数 二 . 字符串操作函数长度不受限制的字符串操作函数1. strcpy函数的使用和模拟实现2. strcat函数的使用和模拟实现3. strcmp函数的使用和模拟实现 长度受限制的字符串操作函数1. strncpy函数的使用2. strncat函数的使用3.…

字符函数strlen、strcpy、strcat、strcmp、strstr、strtok、 strerror和perror函数

目录 1、strlen函数 strlen函数的模拟实现 2、strcpy函数 strcpy函数的模拟实现 strncpy函数 strncpy函数的模拟实现 3、srtcat函数 strcat函数的模拟实现 strncat函数 strncat函数的模拟实现 4、strcmp函数 strcmp函数的模拟实现 strncmp函数 5、strstr函数 st…

anaconda创建了虚拟python环境,且安装了pytorch,但是pycharm中import torch运行时报错

报错如下&#xff1a; C:\Users\tashi\.conda\envs\test1\python.exe D:\project\python\transformer\main.py C:\Users\tashi\.conda\envs\test1\lib\site-packages\numpy\__init__.py:127: UserWarning: mkl-service package failed to import, therefore Intel(R) MKL init…

AI虽强,搜索引擎仍不可或缺

AI 领域正以前所未有的速度发展&#xff0c;大模型的发布变得愈发频繁&#xff0c;模型的规模也在持续扩大。如今&#xff0c;大模型的起点已经攀升至数十亿参数&#xff08;数十 B&#xff0c;B 是 Billion 的简写&#xff0c;10 亿&#xff09;&#xff0c;其功能之广泛&…

二、Python接口自动化fixture和conftest

1、fixture详解 fixture概念fixture是 pytest 用于将测试前后进行预备、清理工作的代码处理机制。 fixture相对于setup和teardown来说有以下几点优势: • fixure命名更加灵活&#xff0c;局限性比较小 • conftest.py 配置里面可以实现数据共享&#xff0c;不需要import就能自…

嵌入式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…

错题记录-华为海思

华为 海思数字芯片 参考 &#xff1a;FPGA开发/数字IC笔试系列(5) 华为海思IC笔试解析 FPGA开发/数字IC笔试系列(6) 华为海思IC笔试解析 SystemVerilog Function与Task的区别 $readmemh与$readmemb这两个系统任务是用来从指定文件中读取数据到寄存器数组或者RAM、ROM中。除了…

stm32与esp8266WIFI模块

硬件介绍 WIFI模块ESP-01S 使用AT指令控制1-ESP8266-AT指令初试化及部分基础知识_ch_pd-CSDN博客 项目需求 通过ESP-01SWIFI模块控制LED状态模拟插座 串口1用于与ESP8266通讯&#xff0c;串口2连接PC&#xff0c;用于打印log&#xff0c;查看系统状态 项目接线 将WIFI模块的…

LLM大语言模型微调方法和技术汇总

本文详细介绍了机器学习中的微调技术&#xff0c;特别是在预训练模型上进行微调的方法和技术。文章首先解释了什么是微调&#xff0c;即在预训练模型的基础上&#xff0c;通过特定任务的数据进行有监督训练&#xff0c;以提高模型在该任务上的性能。随后&#xff0c;详细介绍了…

Sonatype Nexus 服务器迁移

因为服务器的升级和调整&#xff0c;有时候会对安装 Sonatype Nexus 的服务器进行迁移到新服务器上。 从技术架构上来说&#xff0c;Sonatype Nexus 我们使用的是 AWS 的存储&#xff0c;所以我们并不需要拷贝大量的数据。 文件夹结构 在备份和恢复之前&#xff0c;我们需要…

OpenHarmony开源三方库的cmake在IDE上直接引用的问题

前言 DevEco Studio的native工程的C/C部分当前只支持cmake脚本的编译&#xff0c;工程的目录结构如下图所示 在工程中引用第三方库有如下三种方式&#xff0c; 一、find_package模式 通过find_package&#xff0c;可以在指定目录下去搜索已安装的库&#xff08;三方库构建完后…

数字IC/FPGA——亚稳态及跨时钟域

什么是亚稳态亚稳态会造成什么平均故障间隔时间如何解决亚稳态同步时钟和异步时钟单bit电平信号如何跨时钟域单bit脉冲信号如何跨时钟域多bit信号如何跨时钟域 目录 一、亚稳态1.基本概念2.危害3.平均故障时间4.解决亚稳态的方法 二、跨时钟域1.同步电路和异步电路&#xff08;…

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系统读写…

【opencv】示例-npr_demo.cpp 非真实感渲染:边缘保留平滑、细节增强、铅笔素描/彩色铅笔绘图和风格化处理...

Edge Preserve Smoothing- Using Normalized convolution Filter Edge Preserve Smoothing-Using Recursive Filter Detail Enhancement Pencil sketch/Color Pencil Drawing Stylization /* * npr_demo.cpp * * 作者: * Siddharth Kherada <siddharthkherada27[at]gmail[do…

前端小技巧之轮播图

文章目录 功能htmlcssjavaScript图片 设置了一点小难度&#xff0c;不理解的话&#xff0c;是不能套用的哦&#xff01;&#xff01;&#xff01; &#xff08;下方的圆圈与图片数量不统一&#xff0c;而且宽度是固定的&#xff09; 下次写一些直接套用的&#xff0c;不整这些麻…

杭州掀起快递物流创新浪潮,2024长三角快递物流展7月共绘智慧物流新蓝图

杭州&#xff0c;作为中国的电商之都&#xff0c;近年来在快递物流行业背景与应用方面取得了显著的发展。随着电子商务的迅猛增长&#xff0c;杭州的快递物流行业迅速崛起&#xff0c;成为支撑电商产业发展的重要力量。 2024长三角&#xff08;杭州&#xff09;快递物流供应链与…