深度解析C语言——预处理详解

        对C语言有一定了解的同学,相信对预处理一定不会陌生。今天我们就来聊一聊一些预处理的相关知识。预处理是在编译之前对源文件进行简单加工的过程,主要是处理以#开头的命令,例如#include <stdio.h>、#define等。预处理是C语言的一个重要功能,在预处理阶段完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

~~~正文开始~~~

预定义符号

        什么是预定义符号:预定义符号是由编译器预先设置好的特殊标识符,它们代表了特定的信息,如编译器版本、目标平台信息、编译选项等。在C语言中, 也设置了一些预定符号,可以直接使用。

//常见的C语言预定义符号
__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间

使用举例:

int main()
{printf("file:%s line:%d\n", __FILE__, __LINE__);//输出结果:file:C:\Users\test.c line:269printf("date:%s time:%s\n", __DATE__, __TIME__);//输出结果:date:Apr  2 2024 time:17:04:04return 0;
}

#define

#define定义常量

//基本语法
#define name stuff

 使用举例:

//最常见的定义方式
#define MAX 1000
//为register这个关键字,创建⼀个简短的名字。有点类似typedef
#define reg register 
//⽤更形象的符号来替换⼀种实现(死循环)
#define do_forever for(;;) 
//在写case语句的时候⾃动把 break写上。
#define CASE break;case 
//如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__FILE__)

       思考一下,为什么在define定义标识符的时候,后面不加分号呢?我们知道define定义的标识符在预处理阶段就会被替换,如果加上分号,就可能导致程序出错。比如:

#define MAX 10;
int main()
{//替换之后:printf("%d\n", 10;);//就会有语法错误printf("%d\n", MAX);return 0;
}

#define定义宏 

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

//宏的申明⽅式:
#define name( parament-list ) stuff

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

  使用举例:

#define MUL(x) x * x
int main()
{printf("%d\n", MUL(5));//输出:25return 0;
}

上面的代码看上去是不是非常完美?实际上存在了一个特别大的bug,请看下面一段代码:

#define MUL(x) x * x
int main()
{printf("%d\n", MUL(5 + 1));return 0;
}

这段代码的结果是多少呢?36?no no no,实际上上面的代码会被替换成:

printf("%d\n", 5 + 1 * 5 + 1);//结果为11

所以我们在使用宏的时候一定要注意,应该把上面代码修改为:

#define MUL(x) ((x) * (x))

这样就可以得到我们想要的结果了,所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用


宏与函数的对比

宏通常被应用于执行简单的运算。

和函数相比宏的优势

比如在两个数中找出较大的一个时,写成下面的宏,更有优势一些。

#define MAX(a, b) ((a)>(b)?(a):(b))

 优势有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等类型。宏是类型无关的。

和函数相比宏的劣势

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错

宏和函数的对比

 命名约定

⼀般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的用个习惯是:

把宏名全部大写

函数名不要全部大写

但是也有例外,offsetof就是一个宏,但它却是全部小写

#undef

这条指令用于移除一个宏定义。

  使用举例:

#define MAX 20
int main()
{   printf("%d\n", MAX);
#undef MAX  //移除宏定义//printf("%d\n", MAX);  //error//也可以再次定义宏
#define MIN 10printf("%d\n",MIN);return 0;
}

 条件编译

        在编译⼀个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。比如说:调试性的代码,辛辛苦苦写的删除可惜,保留又碍事,所以我们可以选择性的编译。

  使用举例:

#define __DEBUG__
int main()
{int arr[10] = { 0 };for (int i = 0; i < 10; i++){arr[i] = i;#ifdef __DEBUG__       //若为真,则执行printf语句printf("%d\n", arr[i]);//为了观察数组是否赋值成功。#endif //__DEBUG__}return 0;
}

 常见的条件编译指令

  • 条件编译
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
//例:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
  • 多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
  •  判断是否被定义
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol

 使用举例:

#define __DEBUG__ 
int main()
{#if defined(__DEBUG__)printf("haha\n");#endif#if !defined(__DEBUG__)printf("haha\n");#endif//打印结果:hahareturn 0;
}
  • 嵌套指令
#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.hqwc.cn/news/588766.html

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

相关文章

桥式起重机防摇输入整形

资料&#xff1a; 桥式起重机防摇定位控制系统开发&#xff0c;毕江涛 基于输入整形的桥式起重机货物摆动控制策略研究&#xff0c;王冰清 基于输入整形技术的门座起重机吊重摆动控制研究&#xff0c;王云飞 基于变增益 PID 控制的起重机防摇摆设计与仿真&#xff0c;郭瀛舟 ht…

使用Bitmaps位图实现Redis签到

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Redis提供了Bitmaps这个“数据类型”可以实现对位的操作: (1) Bitmaps…

基于单片机的汽车尾灯控制系统设计

**单片机设计介绍&#xff0c;基于单片机的汽车尾灯控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的汽车尾灯控制系统设计概要主要涵盖利用单片机技术实现对汽车尾灯的智能控制。下面将从系统构成、工作…

RPM与YUM

目录 rpm包的管理 介绍 rpm包的简单查询指令 rpm包名基本格式 rpm包的其他查询指令: 卸载rpm包 yum 介绍 rpm包的管理 介绍 rpm用于互联网下载包的打包及安装工具,它包含在某些Linux分发版中.它生成具有.RPM扩展名的文件.RPM是RedHat Package Manager(RedHat)软件包管…

Flutter应用混淆技术原理与实践

在移动应用开发中&#xff0c;保护应用代码安全至关重要。Flutter 提供了简单易用的混淆工具&#xff0c;帮助开发者在构建 release 版本应用时有效保护代码。本文将介绍如何在 Flutter 应用中使用混淆&#xff0c;并提供了相关的操作步骤和注意事项。 &#x1f4dd; 摘要 本…

【数学公式大全整理——1.0】

导数公式 积分表 万能公式 初等函数 重要极限 ### 诱导公式 和差角 和差化积 倍角公式 半角公式 正弦 余弦定理 反三角函数 高阶求导公式

c++对象指针

对象指针在使用之前必须先进行初始化。可以让它指向一个已定义的对象&#xff0c;也可以用new运算符动态建立堆对象。 定义对象指针的格式为&#xff1a; 类名 *对象指针 &对象; //或者 类名 *对象指针 new 类名(参数); 用对象指针访问对象数据成员的格式为&#xff1a…

毕马威:《智慧之眼:开启汽车感知新时代》

在全球科技飞速发展和产业革新的大潮中&#xff0c;汽车产业正在以前所未有的速度向网联化、智能化的方向转型。汽车传感器作为智能联网汽车发展的关键环节之一&#xff0c;扮演着举足轻重的角色。 毕马威一直关注汽车产业的变化与发展&#xff0c;为了更好地为汽车行业赋能&a…

测开——基础理论面试题整理

1. 测试流程 需求了解分析需求评审制定测试计划【包括测试人员、时间、每人负责的模块、测试的风险项以及预防】编写自动化测试用例 —— 测试评审【尽量丰富测试点】编写测试框架和脚本&#xff08;若是功能测试 可省去这步骤&#xff09;执行测试提交缺陷报告测试分析与评审…

Python字符串操作方法一览表

字符串操作 你患得患失太在意从前又太担心将来&#xff0c;有句话说的好昨天是段历史&#xff0c;明天是个谜团而今天是天赐的礼物 像珍惜礼物那样珍惜今天。—— 龟大仙《功夫熊猫3》 1.字符串连接 例子&#xff1a; str1 "Hello" str2 "World" resul…

你知道核相仪的作用和功能吗?使用核相仪要注意哪些事项?

一、核相仪的作用 核相仪是电力系统中一种具有重要检测功能的工具&#xff0c;其主要用途是确定电力线路或变压器两侧之间的相位关系。核相仪的主要作用体现在以下几个方面&#xff1a; 1、相位校验&#xff1a;核相仪的核心功能是准确测定高压或低压电力线路的相位&#xff0c…

华为openEuler-22.03-LTS-SP3配置yum源

先有华为后有天&#xff0c;遥遥领先&#xff01; 1 确定使用的OS版本 # cat /etc/os-release NAME"openEuler" VERSION"22.03 (LTS-SP3)" ID"openEuler" VERSION_ID"22.03" PRETTY_NAME"openEuler 22.03 (LTS-SP3)" ANSI…