C++ | 对比inline内联函数和宏的不同点-1

简介: C++ | 对比inline内联函数和宏的不同点

一、前言

本文我们聊一聊一下宏和内联函数之间的关系。

  • 预处理章节,我们重点介绍了什么是宏,以及宏和函数之间的区别,还记得下面这张图吗,我们对宏和函数之间做了一个很细致的分析:mag:
  • 那在本文中,我们来说的就不是普通的函数了,它叫做【内联函数】,是C++里面独有的,由inline关键字进行修饰,具体怎样,敬请看下去吧👇

image.png

二、宏的优缺点分析

在将内联函数之间前,我们再来聊聊有关宏的一些内容

1、概念回顾

下面是宏的申明方式:

#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

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

  • 在学习了C语言后,如果我不带你去回顾这一些,那你是否能立马写出一个宏来呢,例如:写一个用于求和的宏函数
  • 那我相信对于很多同学来说都会措手不及,答案会像是下面这样千奇百怪🤣
//1.
#define Add(int x, int y) return x + y;
//2.
#define ADD(x, y) x + y
//3.
#define ADD(x, y) (x + y)
//4.
#define ADD(x, y) ((x) + (y));
//5.
#define ADD(x, y) ((x) + (y))
  • 那究竟哪一个对的呢?首先第一个肯定是错误的,因为我说了这是【宏】,而不是函数,这样子是函数的写法。

后面四个的主要区别就在于后面的stuff,我们一一来分析一下

  • 首先若是写成下面这样,传入1给到x,传入2给到y,此时去我们去Linux环境下看看预处理之后会发生什么
int ret = ADD(1, 2);
  • 可以看到很明显进行了一个【宏替换】

image.png

  • 若此时我将调用的函数后面再乘上一个3呢,这会发生什么?
int ret = ADD(1, 2) * 3;
  • 可以观察到此时在预处理阶段也是直接进行了一个替换,不过仔细观察就可以发现,由于*的优先级来得高,所以2会和后面的3先进行一个运算,这也就造成了最后结果的不同

image.png


所以我们应该要像下面这样,在外层加上一个大括号,防止出现优先级的问题

#define ADD(x, y) (x + y)
  • 可是这样真的就可以了吗?若此时我向ADD宏函数传入下面这样的参数呢?会发生什么?
int a = 10;  
int b = 20;
int ret = ADD(a | b, a & b);
  • 编译器还是一样会去做傻傻的替换,但是这个时候我们又得注意优先级的问题了,对于+号来说,它的优先级高于&按位与和|按位或的,如果不清楚优先的话可以看看操作符汇总大全
  • 所以中间的【b】和【a】会先进行结合,然后再去算&|

image.png

  • 如果要防止这种表达式的传入而造成的优先级问题,可以对内部的形参也加上一个括号(),这样就不会出现问题了

#define ADD(x, y) ((x) + (y));
  • 可是呢,有的同学虽然想到了这一点,但是却在最后面加了一个;号,对于分号来说是我们在写代码的时候作为一条语句结束的标志,但是对于宏来说也可以写分号吗?
  • 将原先的传参继续替换成下面这样来试试
int ret = ADD(1, 2) * 3;
  • 继续通过预处理之后的结果去进行观察,就可以发现在进行宏替换之后原先的语句中出现了;号,但是分号后面还有3要乘,这个时候其实就不对了,所以==宏定义后是不可以加分号的==

image.png

  • 最后这一种才是最正确的写法
#define ADD(x, y) ((x) + (y))   //✔

2、宏的缺点

看了上面有关【宏】概念的一些回顾,我们来聊聊它的缺点所在

① 宏可能会带来运算符优先级的问题,导致程容易出现错。【加括号太麻烦了!!!】

  • 这一点相信你在看了上一小节的叙述之后一定是感同身受,只是完成一个两数相加的功能,就需要加上这么多括号了,若是再复杂一些的功能,那岂不是要加很多了🙄

② 宏是不能调试的【这点比较致命👈】

  • 可以观察到,无论是在哪个平台下,对于宏来说都是无法调试的

Windows环境下VS

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/88de8d7fbaf44baba9319a619e17009e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp

Linux环境下gdb

image.png

③ 没有类型安全的检查【直接替换】

  • 可以看到,无论我传入何种类型的参数,都不会出现问题。这点其实说明了宏对于类型的检查是不严谨的

image.png

④ 有些场景下非常复杂,容易出错,不容易掌握

#define SWAP(n) num = (((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1))
  • 乍眼一看,就感觉非常复杂,都是一些二进制的位运算,若是没有对这一块很熟悉的话,就很容易写错,因此说对于一个宏来说其实要实现一些功能的时候会非常繁琐

3、宏的优点

了解了宏的缺点之后,我们再来瞧瞧它的优点,既然在一些场景被广泛使用,那一定也具有它的优点💡

宏常量提高复用性和可维护性

  • 这一点很好理解,也就是我们平常用得最多的,一般会将频繁使用到的一些数字定义成一个宏,这样就不需要每次写数字了,直接写这个宏所定义的变量即可
  • 而且在修改的时候也会很方便,只需要修改一下宏定义处即可,实现一改多改【这一点在项目里面还是比较常用的
#define n 500

宏函数类型不固定,int、double、float都可以用

  • 这一点其实我们在讲缺点的时候也有提及,虽然类型检查得不严谨,但真正用起来还是很香的,这很像是我们之前说到过的函数重载,虽然函数名相同,但是可以传入不同的参数实现不同的运算

宏函数不用建立栈帧,因此不会产生消耗

  • 刚才我们有观察过,对于【宏】来说是不能调试的,因为它根本就不是一个真正意义上的函数,只是具备函数的功能罢了。既然不是函数的话也就不会在栈上建立函数栈帧,那也就不会有过多的消耗

好,以上就是本文所要讲的有关【宏】相关的所有内容,希望能唤起读者对这个知识点的回忆

相关文章
|
2月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
程序员 C++ 开发者
C++入门教程:掌握函数重载、引用与内联函数的概念
通过上述介绍和实例,我们可以看到,函数重载提供了多态性;引用提高了函数调用的效率和便捷性;内联函数则在保证代码清晰的同时,提高了程序的运行效率。掌握这些概念,对于初学者来说是非常重要的,它们是提升C++编程技能的基石。
26 0
|
4月前
|
安全 编译器 C++
C++入门 | 函数重载、引用、内联函数
C++入门 | 函数重载、引用、内联函数
39 5
|
3月前
|
C语言 C++
C++(三)内联函数
本文介绍了C++中的内联函数概念及其与宏函数的区别。通过对比宏函数和普通函数,展示了内联函数在提高程序执行效率方面的优势。同时,详细解释了如何在C++中声明内联函数以及其适用场景,并给出了示例代码。内联函数能够减少函数调用开销,但在使用时需谨慎评估其对代码体积的影响。
|
5月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
5月前
|
算法 编译器 C++
C++基础知识(三:哑元和内联函数和函数重载)
在C++编程中,"哑元"这个术语虽然不常用,但可以理解为在函数定义或调用中使用的没有实际功能、仅作为占位符的参数。这种做法多见于模板编程或者为了匹配函数签名等场景。例如,在实现某些通用算法时,可能需要一个特定数量的参数来满足编译器要求,即使在特定情况下某些参数并不参与计算,这些参数就可以被视为哑元。
101 0
|
25天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
42 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
81 4