缺省参数和内联函数【C++】

在我们用C语言写函数时,在设置函数参数时,是否偶有不确定的时候,“这个参数…用宏…或者全局变量来代替会不会更合适?”就像用C写栈的初始化时,一开始是否要给栈开空间?要开的话开多大呢?设计C++的大佬可能也是意识到了这点,于是便有了缺省参数;用宏写简单函数时,总是会被符号优先级的问题困扰,而且宏函数不会进行类型检查,不用怕!C++推出了内联函数来帮你解决。

缺省参数的规则

缺省参数就是一个有默认值的参数,设有缺省参数的函数,在被调用时,不给缺省参数传参的话,缺省参数就会是默认值,简单举例如下:

#include <iostream>
#include <stdio.h>

void print(int a, int b = 6, int c = 9) {
	std::cout << "a:" << a << std::endl;   // 这里的输出语句不懂的话可以去看看上一篇笔记
	std::cout << "b:" << b << std::endl;   
	printf("c:%d\n\n", c);   // C++兼容C语言哦
}

int main() {
	printf("不缺省:\n");
	print(3, 4, 5);  // 对全部参数都传参时,就和普通函数一样

	printf("完全缺省:\n");
	print(3);   // 不给缺省函数,则该参数为默认值

	printf("部分缺省:\n");
	print(3, 4);  // 缺省函数可以部分缺省,缺省后按参数位置对应传参

	return 0;
}

需要注意的是缺省参数与普通参数需要各自分开,而且缺省参数只能设置在函数参数的尾部,简单思考下:传参是按参数顺序进行传参的,如果普通参数位于缺省参数之后,在参数个数只有一个的情况时,这个参数是传给缺省参数呢?还是后面的普通参数呢?二义性就此产生,所以部分缺省时,缺省参数只能设置在函数参数的尾部,简单举例如下:

// 错误写法:
// 普通参数与缺省参数交错使用
//void print(int a = 1, int b, int c = 3) {

//}

// 虽然将缺省参数与普通参数分开,但是缺省参数未在尾部
//void print(int a = 1, int b = 2, int c) {

//}

// 正确写法:
// 各自分开且缺省参数位于尾部
void print1(int a , int b = 2, int c = 3) {

}

// 当完全缺省时,可任意摆放参数位置
void print2(int b = 2, int a = 1, int c = 3) {

}

当含缺省参数的函数的声明和定义分开时,需要注意的是修改缺省参数时,声明处和定义处都要进行修改,这不免就会有出现失误的时候,而且后续要增添缺省参数时也会很麻烦,其实有一种办法可以解决,就是把声明放头文件里放,只在声明处设置缺省参数,定义处不写缺省参数,全是普通参数,在编译器编译后的链接步骤中,由于函数名相同,且参数的类型及其顺序一样,编译器就会将该声明和定义匹配在一起,变成一个带缺省参数的函数,只需要在声明处修改、增添缺省参数即可,避免声明与定义冲突的情况发生了。

简单应用场景举例

// 初始化栈(C语言版)
#define INITSIZE 4  // 为了避免调用时,需要刻意传参,用宏来定义初始化栈空间的大小

void StackInit(Stack* ps) {
	assert(ps);

	ps->arr = (StDataType*)malloc(sizeof(StDataType) * INITSIZE);
	ps->top = 0;
	ps->capacity = INITSIZE;
}

上述代码在一般场景下是可以过关的,但是在需要频繁扩容时,就不够灵活了,如下:

int main() 
{
	Stack st;
	StackInit(&st);

	for (int i = 0; i < 10; i++) { // 这里入栈10个数据
		StackPush(&st, i);  // 入栈函数,假设已经实现
	}

	for (int i = 0; i < 188; i++) { // 这里入栈188个数据
		StackPush(&st, i);
	}

	// ......后续都是这种知道要入栈数据的个数的循环入栈

	return 0;
}

由于各次入栈的数目不同,如果规定死初始化空间的大小的话,会出现没必要的扩容带来的开销,或许在数据量小的情况下是可以忽略不计的,但是当数据量大起来,异地扩容带来的开销就会挺难受的。
如果使用缺省参数的话,就能完美解决这一问题,同时在正常使用时如果不确定具体入栈数量时,也可以选择缺省参数,开默认值大小的空间,避免了一些精神内耗,哈哈;初始化栈函数如下:

// 初始化栈(缺省参数版)
void StackInit(Stack* ps, int initcapacity = 4) {
	assert(ps);

	ps->arr = (StDataType*)malloc(sizeof(StDataType) * initcapacity);
	ps->top = 0;
	ps->capacity = INITSIZE;
}

这样在使用时便可以随机应变,提高效率的同时也增加了函数的可读性。

内联函数

提问:现在能写一个Add的宏函数吗?
笔者大概可以猜到一些读者的答案:

#define Add(a,b) a+b;  // 预处理时会将;一起替换上去
#define Add(a,b) a+b	// 如果前面有优先级比+更高的运算符的话,会出现错误
#define Add(a,b) (a+b)	// 比上一行好了一些,但还不够,如果a是(2|3)这种比+号优先级低的运算符就会出问题
#define Add(a,b) ((a)+(b))	// 正确答案

宏函数有些时候确实方便一些,能避免一些函数调用带来的花销,但是在实现的时候,要比写普通函数多想一些反逻辑的地方,也不好进行函数的调试,这使得函数的可维护性下降,那么inline函数就很好地解决了这一痛点。
内联函数就是在正常的函数前面加上关键字inline,然后通过将该函数的汇编指令直接在调用处展开的方式来避免call指令带来的花销,达到与宏函数的一样的替换效果,不仅如此,内联函数和正常函数有一样的属性,会进行类型检查以及方便调试等,有一点不同的是内联函数不支持声明和定义分离。
需要注意的是当内联函数过于复杂时,编译器不会展开该内联函数的汇编指令,如内联函数是一个递归函数、内联函数有百八十行等情况,具体多少行后就不展开要看对应编译器,因为inline其实是给编译器发送一个展开的请求,请求是否会被编译器处理就要看设计编译器的大佬是怎么设计的了,要确定你用的编译器是否展开了内联函数,我们可以先调至release版本(debug版本下需要设置一些参数,比较麻烦)然后逐步运行,在内联函数调用处进入反汇编代码查看,如果没有通过call寻址调用,那就是展开了,一般六七十行以下的内联函数是没问题的,所以是有能力替代宏函数的。
虽然使用内联函数能使计算机更加快速地运行代码,不过当大量调用内联函数时,会让程序的空间花销大起来,就是对应的安装包会大一些,因为每次展开的指令都是需要重复存储的,所以在需要大量使用内联函数时,需要斟酌一下是否要将该内联函数设为普通函数使用。

总结

本篇介绍了缺省参数的使用以及内联函数和宏的辨析,含缺省参数的函数的声明和定义分离时需要注意保持一致,也可以采用多文件的方式解决,内联函数有好处但在使用时要注意展开指令带来的空间花销,以及不要将内联函数的声明和定义分离了哦。
给感兴趣的读者出道题:学习缺省参数后,宏函数是否可以被替代呢?用宏定义的常量呢?欢迎在评论区中留下你的答案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值