C语言中宏和函数的9个区别,你都了解吗?

这篇具有很好参考价值的文章主要介绍了C语言中宏和函数的9个区别,你都了解吗?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C语言中的宏和函数是非常相似的,它们都可以完成类似的功能。比如,想要求2个数的较大值,使用宏的写法是:

// 宏的定义
#define MAX(x, y) ((x) > (y) ? (x) : (y))

// 使用
int m = MAX(10, 20);

使用函数的写法是:

// 函数的定义
int Max(int x, int y)
{
	return x > y ? x : y;
}

// 使用
int m = Max(10, 20);

既然宏和函数长的那么像,究竟什么时候用宏,什么时候用函数呢?这就要了解一下它们之间的区别了。我总结了他俩之间的区别,主要体现在以下几点:

  1. 代码长度。
  2. 执行速度。
  3. 操作符优先级。
  4. 带有副作用的参数。
  5. 参数类型。
  6. 调试。
  7. 递归。
  8. 命名约定。
  9. 其他。

C语言中宏和函数的9个区别,你都了解吗?

1.代码长度

宏会在每个使用它的地方替换。比如前面提到的求两个数的较大值的宏,假设这么使用:

int m = MAX(10, 20);
// ...
m = MAX(20, 30)
// ...
m = MAX(30, 40);
// ...
// ...

每个使用宏的地方都会被替换掉。

int m = ((10) > (20) ? (10) : (20));
// ...
m = ((20) > (30) ? (20) : (30))
// ...
m = ((30) > (40) ? (30) : (40));
// ...
// ...

这里的宏体比较短,所以替换进去后,代码的长度并没有明显的提升。但是,假设这个宏有100行代码,每个地方展开后,展开3次,就会多出300行代码。如果更加频繁的调用,调用100次,就会多出10000行代码。所以,当宏体比较长,尤其是调用次数还比较多的情况下,会导致代码长度大大增加。

而函数就不存在这个问题,函数不管调用几次,都只需要写一次函数的代码,每次使用时直接调用即可,代码长度是可控的。

2.执行速度

函数调用时,需要在栈空间上开辟一块栈帧,参数还要压栈。当函数体的代码执行完后,需要返回时,还要销毁栈帧。这些都是有开销的,执行速度较慢。

但是,宏的代码在预处理阶段就已经完成替换,不存在这个问题,执行速度较快。

3.操作符优先级

使用宏时,代码是在对应的位置直接展开,如果该位置周围有其他操作符,有可能干扰宏体内的操作符的执行顺序,导致错误的结果。比如:

#define DOUBLE(x) x + x

如果这么调用:

int ret = 2 * DOUBLE(10);

我们想的是:DOUBLE(10)会算出20,再乘2,得到40。然而,实际代码会这样展开:

int ret = 2 * 10 + 10;

由于乘号的优先级比较高,会先算2*10,得到20,再加10得到30,和预期的结果不一致。

但函数不存在这个问题。

int Double(int x)
{
	return x + x;
}

当这样调用时:

int ret = 2 * Double(10);

一定是先把10传给函数,函数计算完后返回20,再进行别的计算。

当然,如果参数本身是表达式时,也会有相同的问题。比如:

#define SQUARE(x) x * x

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

int ret1 = SQUARE(3 + 2);
int ret2 = Square(3 + 2);

函数就是正常的,先计算3+2得到5,在把5传参,得到25。但是宏会这样替换:

int ret1 = 3 + 2 * 3 + 2;

由于乘号的优先级较高,得到的结果就是11,和预期结果不符。

为了解决这样的问题,建议写宏时多加括号,防止受到其他操作符的影响。比如:

#define DOUBLE(x) ((x) + (x))
#define SQUARE(x) ((x) * (x))

4.带有副作用的参数

对于MAX宏,如果这样使用:

int x = 3;
int y = 5;
int m = MAX(x++, y++);

我们的想法是:把x和y传参,算出x和y的较大值为5,即m应该是5,而后置++会把x和y的值分别改成4和6。但是实际替换时是这么替换的:

int m = ((x++) > (y++) ? (x++) : (y++));

计算时,先判断x++>y++这个表达式,显然x<y,故为假,判断完后x和y都要++,x改成4,y改成6,返回y++的结果,即6,y再++改成7。所以最终结果是:m为6,x为4,y为7,和预期不符,原因是带有副作用的宏参数影响了结果。

函数就不存在这个问题。如果调用Max函数:

int x = 3;
int y = 5;
int m = Max(x++, y++);

由于函数的传参和函数体代码的执行是分开的,所以结果和预期相同,m=5, x=4, y=6。

5.参数类型

宏是直接对代码进行文本替换,是不检查类型的。比如:

int m1 = MAX(3, 5);// 会被替换成int m1 = ((3) > (5) ? (3) : (5));
double m2 = MAX(3.2, 5,3); // 会被替换成double m2 = ((3.2) > (5.3) ? (3.2) : (5.3));

但是函数的形参是有类型的,传参时会对类型进行检查。比如前面的Max函数,参数列表是(int, int),只能求2个整数的较大值,如果要求两个浮点数的较大值,是无能为力的。

6.调试

宏直接完成代码的替换,不方便调试。因为宏的替换在预处理阶段已经完成,但是调试时调试的是最终生成的可执行程序。由于已经完成了替换,看到的代码和调试的代码是不一样的。函数就没有这个问题,可以逐语句调试。

7.递归

宏不能递归,函数可以实现递归。

8.命名约定

一般宏的名字为全大写,函数名不会全大写。比如:

  1. 宏名:MAX, DOUBLE, SQUARE。
  2. 函数名:Max, Double, Square。

但是也有例外。比如库中的offsetof是一个宏,而不是函数。

9.其他

宏可以实现直接把参数转换成字符串,或者把两个标识符连起来。具体可以看我之前写的这篇博客:你知道C语言中的#和##分别是什么意思吗?

总结

  1. 宏的代码长度较长,因为会被多次替换。函数只存一份代码,长度较短。
  2. 函数调用和返回都有开销,速度较慢。宏速度较快。
  3. 宏会受到操作符优先级的影响,导致结果可能和预期不符,函数没有这个问题。解决方案:写宏时,最好多加括号来限定操作符的执行顺序。
  4. 宏有可能受到带有副作用的参数的影响,函数无此影响。
  5. 函数有参数类型检查,宏没有。
  6. 宏不方便调试,函数无此问题。
  7. 函数可以递归,宏不可以。
  8. 宏名一般全大写,函数一般不会全大写。
  9. 宏可以实现一些函数实现不了的操作,比如把参数直接转换成字符串,连接2个标识符等。

根据以上几点,一般来说,一些简单的逻辑可以使用宏实现,比如求2个数的较大值。但是当代码长度比较长,或者逻辑比较复杂时,建议使用函数实现。

感谢大家的阅读!文章来源地址https://www.toymoban.com/news/detail-409165.html

到了这里,关于C语言中宏和函数的9个区别,你都了解吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【面试题】关于JavaScript实现继承的六大方案,你都了解过吗?

    ​ 前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库  web前端面试题库 VS java后端面试题库大全 面试官:“你说说 JavaScript 中实现继承有哪几种方法?” 紧张的萌新:“额,class 中用 extends 实现继承,然后...没了...” 面试官:“...” ······ 想必绝大

    2024年02月02日
    浏览(29)
  • 一文了解Go语言的函数

    函数是编程中不可或缺的组成部分,无论是在 Go 语言还是其他编程语言中,函数都扮演着重要的角色。函数能够将一系列的操作封装在一起,使得代码更加模块化、可重用和易于维护。 在本文中,我们将详细介绍Go语言中函数的概念和使用方法,包括函数的定义、参数和返回

    2024年02月09日
    浏览(34)
  • 一文了解Go语言的匿名函数

    无论是在 Go 语言还是其他编程语言中,匿名函数都扮演着重要的角色。在本文中,我们将详细介绍 Go 语言中匿名函数的概念和使用方法,同时也提供一些考虑因素,从而帮助在匿名函数和命名函数间做出选择。 匿名函数是一种没有函数名的函数。它是在代码中直接定义的函

    2024年02月10日
    浏览(29)
  • C语言:Strlen()函数你了解多少?

    C语言中strlen是一种函数,主要用于计算字符串的长度。 🚩 strlen()从字符串的开头位置依次往后面计数,直到遇到‘\\0’停止,所计算的字符串大小为‘\\0’以前的字符所计算的值,最终的字符串长度不包括‘\\0’ 🚩 strlen因为是一种函数,因此调用这个函数需要引用的头文

    2024年02月06日
    浏览(59)
  • 人工智能之深度学习常见应用方向你都了解吗?(文末包邮送书5本)

    从零带你了解深度学习常见的7大应用方向,包括:数字识别、图像识别、图像分类、目标检测、人脸识别、文本分类、聊天机器人。 🔥🔥本文已收录于专栏:《极客日报》,欢迎免费订阅 ​此专栏用于分享前沿技术、行业资讯、科技热点、工具测评、优质IT书籍和 抽奖包

    2024年02月03日
    浏览(37)
  • 深入了解 CSS 中的 :where() 和 :is() 函数

    :where() 函数接受一个选择器列表作为参数,允许你编写更少的代码并同时设置它们的样式。在本文中,我们将讨论 :where() 伪类函数,并演示如何在生产环境中使用它。我们将回顾与 :where() 函数相关的叠加、优先级和安全性。我们还将研究一些特定的用例,并讨论它与 :is() 函

    2023年04月08日
    浏览(30)
  • 深入了解Python中的os.path.join函数

    在Python中,处理文件和目录路径是常见的任务。为了简化路径的拼接和操作,Python提供了 os.path 模块,其中的 join 函数是一个非常重要且常用的函数。本文将深入介绍 os.path.join 函数的用法和注意事项,以帮助读者更好地理解和使用该函数。 os.path 模块是Python中用于处理文件

    2024年02月09日
    浏览(28)
  • 深入了解PBKDF2:密码学中的关键推导函数

    title: 深入了解PBKDF2:密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学 对称加密 哈希函数 KDF PBKDF2 安全 密钥派生 对称加密和哈希函数 对称加密 :对称加密是一种加密技术,使用相同的密钥进行加密和解密。常见的对称加密算法有AES、DES等。发送

    2024年04月22日
    浏览(25)
  • C语言从入门到实战——常用内存函数的了解和模拟实现

    内存函数(memory functions)指的是控制计算机内存操作的函数 函数 memcpy 从 source 的位置开始向后复制 num 个字节的数据到 destination 指向的内存位置。 这个函数在遇到 \\\'\\0\\\' 的时候并不会停下来。 如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。 对于重叠的内存

    2024年02月03日
    浏览(21)
  • c语言函数指针和指针函数的区别,以及回调函数的使用。

    函数指针 是什么,函数指针本质也是指针,不过是指向函数的指针,存储的是函数的地址。 指针函数 是什么,指针函数其实就是返回值是指针的函数,本质是函数。 函数指针是如何定义的呢,如下 这里 * pfun 需要 加括号 ,否则定义为了指针函数。 指针函数的定义如下: 下面

    2024年02月11日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包