【C++】内联函数vs宏 && nullptr

宏的优缺点分析

在这里插入图片描述

概念回顾

下面是宏的申明方式:

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

例如:

#define SQUARE( x ) x * x
  • 这个宏接收⼀个参数 x .如果在上述声明之后,你把 SQUARE( 5 ); 置于程序中,预处理器就会⽤
    下⾯这个表达式替换上⾯的表达式: 5 * 5

注:① 参数列表的左括号必须与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,我们一一来分析一下

那第二个对了吗?
看看这个:

int ret = ADD(1, 2) * 3;

  • 可以观察到此时在预处理阶段也是直接进行了一个替换,不过仔细观察就可以发现,由于*的优先级来得高,所以2会和后面的3先进行一个运算,这也就造成了最后结果的不同
    在这里插入图片描述
  • 所以我们要在外层加上一个大括号防止出现优先级问题
#define ADD(x, y) (x + y)

  • 那这样就行了吗,如果我们向ADD函数这样传参呢?
int a = 10;  
int b = 20;
int ret = ADD(a | b, a & b);

  • 编译器还是一样会去做傻傻的替换,但是这个时候我们又得注意优先级的问题了,对于+号来说,它的优先级高于&按位与和|按位或的,
  • 所以中间的【b】和【a】会先进行结合,然后再去算&和|
    在这里插入图片描述
  • 如果要防止这种表达式传参出现优先级的问题,那么我们就应该为参数加上括号
#define ADD(x, y) ((x) + (y));

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

宏的缺点

  • 看了以上的代码,就知道宏有一个明显的缺点,定义太麻烦了,哪里都要加括号
  • 而且宏是不能像函数一样调试的,这样我们在写错宏的时候就很难排查哪里出了问题
  • 宏也没有类型的检查,无论我们传入那种类型的参数,都不会出问题,那么就会造成算术混乱

宏的优点

  • 宏提高了可复用性和可维护性,比如我们一般用到宏最多的地方就是把一个数字定义成一个宏,如一个数组我们刚开始想要是10个空间,但是后面不够,就可以直接更改宏来替换,不用一句句去改变
#define n 500

  • 宏不用创建函数栈帧,这就不会造成内存的消耗

内联函数(inline)

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率

inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}

  • 但是他真的不会建立函数栈帧吗?

在这里插入图片描述

这里有call指令(call就是调用函数),调用了函数就会创建函数栈帧。难道是骗我们的?

  • 其实我们还需要去做一些配置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 然后我们再去观察一下汇编就可以发现不存在call指令了,编译器直接将内敛函数Add做了展开
    在这里插入图片描述

  • 所以C++中我们是不推荐用宏的,因为有内联函数这个特性,即保留了宏的优点,无需调用函数建立栈帧,而且还修复了宏的缺陷,不再需要将内容写得那么复杂,写成日常的函数形式即可,只需要在前面加上一个inline关键字,就可以起到这种效果。非但如此,它还可以调试

  • vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试

注意:vs的release版本是默认展开的

  • C++标准没有规定默认展开inline函数,是否展开这个函数取决于编译器本身,编程器通常只展开短小函数的内联函数(10行代码以内),不会对递归函数或者迭代展开
    在这里插入图片描述
  • 这其实就是内联函数在替代宏之后很优秀的一个特性,假设说现在你这个设置的内联函数有1000多行代码,在一个大项目中又有1000个地方调用了这个内联函数。
  • 如果采用内联将其展开的话消耗的就是1000 * 1000条指令

inline函数的定义和声明

  • inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址
    址,链接时会出现报错。
// F.h
#include <iostream>
using namespace std;

inline void f(int i);
//--------------------------
// F.cpp
#include "F.h"

void f(int i)
{
	cout << i << endl;
}
//--------------------------
// main.cpp
#include "F.h"

int main()
{
	f(10);
	return 0;
}

  • 解决这个问题就直接把定义和声明写到同一个头文件中

在这里插入图片描述
这样内联函数在展开的时候就可以找到地址

#nullptr

  • C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种
    定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的
    f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL);
    调⽤会报错。
#ifndef NULL
#ifdef __cplusplus
	#define NULL 0
#else
	#define NULL ((void *)0)
#endif
#endif

所以我们推荐在C++中使用nullptr代表空指针

总结

  • 引入内联函数的目的实际上就是为了补充C语言中宏的缺陷,如优先级问题,不能调试问题
  • 但是内联函数用的时候也要注意C++标准没有规定默认使用内联函数,取决于编译器。这种空间换时间的思想只适用于小型的函数,对于大型的函数不建议定义成【内联函数】,会造成程序的过多臃肿
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值