IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言】预处理详解,宏与函数的区别对比 -> 正文阅读

[C++知识库]【C语言】预处理详解,宏与函数的区别对比

一些内置的预定义符号

我们知道在预处理阶段会替换#define定义的标识符和宏,今天我们就向大家介绍一下有关#define相关的知识,#define的作用有两个:

1、#define定义标识符
2、#define定义宏

我们先来看看C语言中有哪些内置预定义符号

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

我们可以在vs编译器中尝试使用这些预定义符号

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

在这里插入图片描述

#define定义标识符

使用#define定义标识符的语法:

#define name stuff

我们通过举例子说明
#define MAX 100
我们使用上面的代码可以定义一个标识符MAX,他代表的内容就是100。

#define MAX 100
int main()
{
	printf("%d", MAX);
	return 0;
}

在这里插入图片描述
实际上在经历预编译过程之后,代码就会被替换成下面这个样子

int main()
{
	printf("%d", 100);
	return 0;
}

#define P printf
我们觉得输出函数printf太长了,每次写起来太麻烦了,是否可以将他的名字简化呢?使用上面的代码我们就可以创建一个简短的名字。

#define MAX 100
#define P printf
int main()
{
	P("%d", MAX);
	return 0;
}

在这里插入图片描述
我们运行代码后打印出来我们想要的值。同样在经历预处理后上面这个代码也变成了

int main()
{
	printf("%d", 100);
	return 0;
}

#define还可以干什么呢?
#define FOR for(;;)
我们在代码中运行一下上面这行代码,看看他是什么效果。

#define FOR for(;;)

int main()
{
	FOR
	{
		;
	}
	printf("100");
	return 0;
}

在这里插入图片描述
结果并没有打印100,说明这行代码在预处理后变成了

int main()
{
	for(;;)
	{
		;
	}
	printf("100");
	return 0;
}

这样就会死循环下去printf不会被打印,这种情况说明,我们可以自己创建一个符号来表示一种实现

#define加不加;

我们学了这么长时间的C语言,我们都知道在一段语句的后面需要加上;来代表语句的结束。那么#define定义表示符的时候,需不需要在最后加上;答案是不需要,因为我们的#define定义的标识符在替换时是全部替换,如果使用;会造成一些错误,例如:

#define MAX 100
#define MAX_T 100;

int main()
{
	printf("%d", MAX);
	printf("%d", MAX_T);
	return 0;

}

我们执行代码
在这里插入图片描述
编译器报错了,这是什么原因呢?我们#define定义过的标识符进行替换

#define MAX 100
#define MAX_T 100;

int main()
{
	printf("%d", 100);
	printf("%d", 100;);
	return 0;

}

这样我们就发现了问题,当#define定义标识符的时候加上;就会出现了问题,因为我们会将;一起替换进代码中,所以会造成一些错误,所以我们在使用#define定义标识符时,不要在最后加;

#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
语法:#define name( parament-list ) stuff其中的parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。需要注意的是参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。例如:我们计算两个数相乘,我们可以使用函数,也可以使用宏。
函数实现

int sub(int x)
{
	return x * x;
}

int main()
{
	int a = 5;
	int c = sub(a);
	printf("%d", c);
	return 0;
}

宏实现

#define sub(x) (x*x)
int main()
{
	int a = 5;
	int c = sub(a);
	printf("%d", c);
	return 0;
}

这两种代码都可以实现我们需要的乘法,但是好像有一点问题,如果我们将代码更改为

#define sub(x) (x*x)
int main()
{
	int a = 5;
	int c = sub(a+1);
	printf("%d", c);
	return 0;
}

我们预期的结果是6*6等于36,但实际结果呢?最后输出的结果为11,这是为什么呢?因为在预处理阶段,我们替换宏的时候,参数x被替换为了a + 1,实际上这条语句为int c = sub(a +1 * a + 1)因为乘号的优先级高于加号,所以最后结果为11
在这里插入图片描述
出现这样的状况有没有解决的办法呢?当然是有的,我们只需要在参数x两边加上括号就可以了。

#define sub(x) ((x)*(x))
int main()
{
	int a = 5;
	int c = sub(a+1);
	printf("%d", c);
	return 0;
}

在这里插入图片描述
由此我们总结了一点,我们在使用宏定义数值表达式的求值时都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
#define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

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

#和##

使用#可以把一个宏参数变成对应的字符串。
使用##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。

我们举两个例子来展示一下# ##的作用吧

int main()
{
	int d = 0;
	int a = 1;
	int c = 2;
	printf("d = %d", d);
	printf("a = %d", a);
	printf("c = %d", c);
}

我们希望以上面的方式打印每个变量值,但是这样写太过麻烦,我们可不可以写一个宏来实现我们的需求,每次我们只需要使用宏就可以了。

#define PRINT(V,F) printf(#V" = "F"\n",V)

int main()
{
	int a = 0;
	int c = 0;
	PRINT(a, "%d");
	PRINT(c, "%d");
	return 0;
}

因为字符串是有自动链接的特点的,所以我们可以这样写代码,在预处理之后的代码是这样的

int main()
{
	int a = 0;
	int c = 0;
	printf("a"" = ""%d""\n", a);
	printf("c"" = ""%d""\n", c);

	return 0;
}

这样我们就实现了我们的需求,如果我们不使用#直接#define PRINT(V,F) printf("V"" = "F"\n",V)这样的效果会是什么样呢?让我们来执行一下
在这里插入图片描述
这样就会把打印的数据写死,所以大家有没有理解#的用法。下面我们在来看看##

#define CAT(A,B) A##B
int main()
{
	int helloworld = 199;
	printf("%d", CAT(hello, world));
	return 0;
}

在这里插入图片描述
使用##可以将宏的两个参数拼接起来,在预处理后代码就会变成printf("%d", helloworld);所以打印结果为199。

带副作用的宏参数

为什么使用宏还会有副作用?这个副作用是什么?我相信大家都会有这样的疑问,我们还是先举个例子

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

int main()
{
	int a = 0;
	int b = 3;
	int c = MAX(a++, b++);

	printf("%d %d %d",a,b,c);
	return 0;
}

我们来看看这个代码,我们的本意是将a++ b++后的值进行比较将较大值赋值给c,在输出abc三个变量各自的值,但是真正输出的值是这样嘛?

在这里插入图片描述
我们的预期结果应该是1 4 4,但实际输出是1 5 4这是为什么呢?我们知道在预处理后的结果是c = ((a++) > (b++) ? (a++) : (b++));所以我们的结果是1 5 4。这就是带有副作用的宏参数。

宏和函数的对比

代码长度

#define定义宏:每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。
函数:函数代码只出现于一个地方,每次使用这个函数时,都调用那个地方的同一份代码。

例如:我们的需求是计算两个数相加,我们可以使用函数,也可以使用宏实现,但是使用宏完成需求,在预处理后会进行宏替换,增加代码的长度。

int add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 0;
	int b = 0;
	int c = add(a, b);
	int d = add(a, a);
	int e = add(b, b);
	printf("%d %d %d", c, d, e);
	return 0;
}
#define ADD(x,y) ((x) + (y))

int main()
{
	int a = 0;
	int b = 0;
	int c = ADD(a, b);
	int d = ADD(a, a);
	int e = ADD(b, b);
	printf("%d %d %d", c, d, e);
	return 0;
}

经过替换后

int main()
{
	int a = 0;
	int b = 0;
	int c = (a) + (b);
	int d = (a) + (a);
	int e = (b) + (b);
	printf("%d %d %d", c, d, e);
	return 0;
}

可能这个例子还不能体现出代码量的增加,因为我们这个宏的非常小,如果我们设计了一个很复杂的宏,并且经过多次调用,就会使代码量大幅度增长。
执行速度

#define定义宏:比函数更快。
函数:存在函数调用和返回的额外开销,所以相对更慢一些。

还是上面的例子:我们的需求还是求两个数相加

int add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 0;
	int b = 0;
	int c = add(a, b);
	int d = add(a, a);
	int e = add(b, b);
	printf("%d %d %d", c, d, e);
	return 0;
}

使用函数时,我们需要为add这个函数在内存中开辟一块空间,我们成为函数栈帧,这个过程会花费时间,如果我们需求非常小,所需要的时间还没有函数栈帧所花费的时间多,我么使用函数完成需求得不偿失,所以我们可以使用宏完成需求,宏的使用不需要开辟内存空间。

操作符优先级

#define定义宏:宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。
函数:函数的参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预料。

带有副作用的参数

#define定义宏:参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。
函数:函数的参数只在传参的时候求值一次,结果更容易控制。

参数类型

#define定义宏:宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。
函数:函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。

我们的需求还是两个数相加:

int add(int x, int y)
{
	return x + y;
}

float add_f(float x, float y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = add(a, b);
	float d = 10.5;
	float e = 20.5;
	float f = add_f(d, e);
	printf("%d %.1f", c, f);
}

如果使用函数,我们在进行不同类型的变量相加时,我们需要定义不同返回值类型和参数类型的函数,即使他们的函数体完成的功能一样。

#define ADD(x ,y) ((x) +(y))

int main()
{
	int a = 10;
	int b = 20;
	int c = ADD(a, b);
	float d = 10.5;
	float e = 20.5;
	float f = ADD(d, e);
	printf("%d %.1f", c, f);
}

使用宏我们就不需要考虑参数的类型。
调试

#define定义宏:宏是不方便调试的。
函数:函数是可以逐语句调试的。

递归

#define定义宏:宏是不能递归的。

函数:函数可以递归。

命名的约定

一般来说函数和宏的使用语法很相似,所以语言本身没法帮我们区分二至,所以我们需要通过名字去区分他,所以在取名时我们的一个习惯是:

宏:把宏的名字全部大写。
函数:函数名不要全部大写。

undef

使用这条指令可以移除一个宏定义。#undef NAME

#define ADD(x ,y) ((x) +(y))
//#undef ADD

int main()
{
	int a = 10;
	int b = 20;
	int c = ADD(a, b);
	float d = 10.5;
	float e = 20.5;
	float f = ADD(d, e);
	printf("%d %.1f", c, f);
}

在这里插入图片描述

#define ADD(x ,y) ((x) +(y))
#undef ADD

int main()
{
	int a = 10;
	int b = 20;
	int c = ADD(a, b);
	float d = 10.5;
	float e = 20.5;
	float f = ADD(d, e);
	printf("%d %.1f", c, f);
}

在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 20:56:54  更:2022-10-22 20:57:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 7:06:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码