当前位置: 首页 > news >正文

#预处理和函数的对比以及条件编译

预处理详解

  • 预定义符号
  • #define
    • #define定义宏
    • #和##
    • 宏和函数的对比
  • #undef和条件编译

前言:本来不打算写着一篇博客的,总觉得这一部分的知识以后用的话直接再来查一下就好了,没有看的太重,但是后来又想了想,初学者的路上还是要养成善于总结和归纳的习惯比较好,因此写下了这一关于预处理的总结博客,将预处理和函数做了一个对比(举例说明),此后还介绍了条件编译的相关内容,希望能对初学者有一定的帮助

预定义符号

大家一提到预处理符号可能第一时间想到的就是#define关键字,在介绍#define的内容之前我们先来介绍一下C语言内置的预定义符号作为某些同学的扩展。

FILE //进行编译的源文件
LINE //文件当前的行号
DATE //文件被编译的日期
TIME //文件被编译的时间
STDC //如果编译器遵循ANSI C,其值为1,否则未定义

既然是C语言内置的,那么就是可以直接使用的,接下来我给大家写段代码演示一下,大家跟着注释走一遍这里就可以过去了,没什么要说的。

#include <stdio.h>

int main()
{
	printf("file name:%s\nline:%d\n", __FILE__, __LINE__);
	//注意这里的时间是文件编译的时间,不是文件运行的时间
	printf("TIME:%s\n", __TIME__);
	//__STDC__//VS并不是完全支持ACSI C的,所以这里是未定义
	printf("holle world!\n");

	return 0;
}

在这里插入图片描述

#define

#define在我们日常写代码时候可能用的还是比较少的,但是我在翻看一些书籍的时候,发现大佬们经常使用#define来表示某种状态或者用来提高代码的逻辑可读性,这一点还是需要我们借鉴和学习的。下面我们就来介绍#define和#define有关的一系列知识点。
语法:

#define MAX 100
#define FOR for( ; ; )
#define DOU double //为doube类型重新命名

#define MAX 100
#define FOR for (; ; ) //死循环
#define DOU double     //为doube类型重新命名

int main()
{
	int a = MAX;
	DOU f = 3.14;

	printf("%d %lf\n", a, f);

	return 0;
}

这是#define最简单的一种基本用法,很简单,但是仍然有需要我们注意的点。
#define的最后我们一般是不用加上 ; 的,这可能会造成我们在使用时的预防错误
在这里插入图片描述
关于#define的功能还有很多,并不只有上面简单的用法。

#define定义宏

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

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中
注意:
( parament-list )与name之间是没有空格的,与stuff之间可以存在一个或者多个空格。
比如如下的宏定义:
在这里插入图片描述

这是不是和我们的函数有一些相似呢,但是函数和宏哪个更好呢?我们之后再给大家介绍宏和函数对比的相关内。
大家再是思考一个问题,如果我在这里传入的是以下的内容呢?

SUM(1+1, 2+2);

此时ret的结果是什么呢?
有同学可能认为简单嘛,1+1=2 2+2 = 4 24 = 8,结果ret=8嘛。
在这里插入图片描述
最终的结果却是5!!
其实是这样的,宏在传参的时候,并不会像函数那样,会先对实参表达式进行运算,再传给形参,这里传过去的是1+1和2+2,那么此时的SUM就是1+1
2+2
即1+2+2 = 5。
我们只需要在宏体部分做一些修改就好了。

#define SUM(a, b) (a)*(b)

这样就能达到我们想要的结果了。
所以

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

宏的替换规则:

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

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

我们以下面的代码为例,解释一下上面的规则:

#define MAX 10
#define PRI printf("%d\n", MAX)

int main()
{
	PRI;
	return 0;
}

在程序预编译阶段会扫描宏体中是否含有#define定义的符号,如果有,会先将宏体中符号发生替换,即:

#define PRI printf(“%d\n”, 10)

一个源文件在变成一个.exe的可执行文件时,会经历几个阶段,第一个就是预编译阶段,然后就是编译,汇编,链接这几几个过程,大家对这一方面感兴趣的话可以去查一下相关的优质博客,了解一下。

#和##

了解完#define和宏之后我们接着就来介绍一下#和##的作用。
首先我们看看这样的代码:

char* p = “hello ““bit\n”;
printf(“hello”” bit\n”);
printf(“%s”, p);

这里的输出是hello bit

我们发现字符串是有自动连接的特点的!!
在知道这个前提条件之后我们来看下段代码
在这里插入图片描述
我们发现将#将FORMAT转换成了一个字符串,上面的代码就相当于

printf(“the value is ““FORMAT””\n”, VALUE)

所以#可以把一个宏参数变成对应的字符串

接下来我们再来看一下##的作用,同样还是以一段代码为例
在这里插入图片描述
我们发现##有以下特性

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符

关于##的实际运行可能不多,但是这写知识点我们还是要知道的,正所谓“存在即合理”。

宏和函数的对比

学习完有关#define定义符号和宏的有关知识点后,我们发现在有时候函数能做到的事情,宏同样也可以做的到,那么宏和函数对比它们各自的优缺点是什么呢?什么时候用宏什么时候使用函数呢?
下面我们就来一一介绍。

我们这里分别给出使用宏和函数实现取两个数中的较大值的代码。
在这里插入图片描述
显然这两者都能实现相应的功能,那么如果让你选择,你会选择那一种方法呢?
要想看出两者的区别,我们直接观察是看不出来的,我们通过反汇编的角度再来看一下这段代码的情况
main函数内部
在这里插入图片描述
Max函数内部
在这里插入图片描述
我们可以很清楚的看到如果用函数的方式来实现这一小功能的话,远不如使用宏来的更简单一些(反汇编代码越多代表着消耗的时间越长)。
所以我们在实现的一些简单的功能时,比如比较大小,或则取较大值可以使用宏来代替函数,会提升我们代码的运行效率。
还有一点就是大家有没有注意到,宏在比较大小的时候,是不需要指定格式的,它并不像函数那样,需要指定是int类型还是float类型,这一点还是比较好的。

那是不是宏就是比函数要好呢?
当然不是。

我们知道#define定义的宏是在预编译阶段对文本中的内容进行替换的

#define STR(a, b) #a#b 
int main()
{
	char* p = STR(holle, world);
	//预编译之后的结果:char* p = "holle""world";
	printf("%s\n", p);
	return 0;
}

那么假设我们的宏体很长,有几十行代码,那么我们在预编译的阶段就会多出很多行的代码,会占用较多的内存,并不好。

再者,宏能实现的功能是很有限的,它并不像函数那样灵活,可以处理一些错误情况和复杂功能。

所以综合各种情况,给出一下表格

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的
调式宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

因为宏在预编译阶段就已经完替换,而调试代码是在完成链接之后的,所以#define定义的符号和宏是无法调试管查的。

#undef和条件编译

最后是关于#undef和条件编译的介绍,两者基本上都是成对出先的。

先来介绍#undef的用法。
这条指令用于移除一个宏定义。

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

假设我们想要取消对NAME的定义,就可以这样做
在这里插入图片描述
条件编译,从字面意思上也就是满足条件就进行编译,不满足就不进行编译
比如说

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译

#include <stdio.h>
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
			printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

如果__BEBUG被定义了,那么就编译printf(“%d\n”, arr[i]);,反之不进行编译。
在这里插入图片描述
在这里插入图片描述
条件编译的方式有很多种

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

4.嵌套指令
#if defined(OS_UNIX)
	#ifdef OPTION1
		unix_version_option1();
	#endif
	#ifdef OPTION2
		unix_version_option2();
	#endif

#elif defined(OS_MSDOS)
	#ifdef OPTION2
		msdos_version_option2();
	#endif
#endif

大家多做做练习这些内容就能很好的掌握住了。

相关文章:

  • 陕西优秀的企业门户网站建设/百度搜索网页
  • 手机网站模板 学校/百度商业平台
  • 学做奶油的网站/福州seo
  • 线上交易商城平台开发/网站seo基本流程
  • 网站推广中应注意哪些事项/网站怎么制作教程
  • 什么平台可以发广告免费/金融网站推广圳seo公司
  • NPP净初级生产力数据获取/气象数据/太阳辐射/NDVI数据
  • 【计算机毕业设计】基于微信小程序的驾校学车预约服务系统
  • Linux技巧(六):命令尾部的作用 | 、、|、||、;、()的区别和作用(很实用)
  • asp.net设备实时管理系统VS开发sqlserver数据库web结构c#编程计算机网页项目
  • 『 云原生·Docker』Docker容器相关操作(二)
  • 04.好客租房_搭建自己的图床,接入swagger-ui,新增房源列表接口编写
  • Spring Security
  • MySql(35)索引的设计原则
  • 公众号网课搜题功能搭建
  • BUUCTF NewStarCTF 公开赛赛道Week4 Writeup
  • qt creator ui界面修改后运行不产生作用(本质分析)
  • 【Linux线程同步专题】四、信号量