以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/nj0C9SbAu...
自从上一篇关于 C 语言单个参数函数的默认值实现《C语言函数也可以给形参添加默认值?》发表以来,有很多的同学反馈想知道多参数函数的默认值又该如何实现,今天特地整理相关代码和实现思路说明如下。
由于 C 语言本身是不提供函数默认值功能和相关语法的,为了实现函数参数的默认值,归根到底是对原有函数参数的自动填充,填充的值就是默认值。
那么如何填充参数值,又不会影响程序性能呢?可以使用宏定义的灵活性在代码的编译预处理期做一些替换操作,本文和《C语言函数也可以给形参添加默认值?》文章的基本思路类似,但是实现上会繁琐一些,最终的目标是实现从单参数默认值向多参数默认值迭代升级。
设想一下总体思路:
由于带有默认值的函数在使用时,形式上输入的参数个数是不定的,所以需要计算函数实际输入参数的个数,然后针对未输入的参数填充默认值。所以这里提出两个问题,一个是如何计算函数实际输入参数的个数,另一个是如何对参数填充默认值?
接下来将对提出来的问题逐一破解。
计算函数实际输入参数的个数
我们先来定义一个带有 2 个输入参数的目标函数 _fun2():
perl
void _fun2(int val1, int val2)
{
printf("fun inputs val1:%d, val2:%d\n", val1, val2);
}
参考《C语言函数也可以给形参添加默认值?》文章的基本思路,定义一个变长参数宏 fun2() 代表目标函数 _fun2(),原来调用目标函数 _fun2() 的语句将变成直接调用宏 fun2(),而目标函数 _fun2() 间接被调用。
但是由于我们的思路里多了一个因素---函数实际输入参数的个数,所以变长参数宏 fun2() 和目标函数 _fun2() 之间还需要多一层转换,下面统称为转换函数 _funs(),函数实际输入参数(调用宏 fun2() 时输入的参数)的个数作为参数输入到转换函数 _funs(),目标函数 _fun2() 在转换函数 _funs() 内部被调用。
scss
#define fun2(...) _funs(ARGC(__VA_ARGS__), 123, 456, ##__VA_ARGS__)
void _funs(int real_param_num, ...)
{
...
// _fun2(val1, val2);
}
宏定义 ARGC() 用于计算函数实际输入参数的个数,123 和 456 作为目标函数 _fun2() 的参数默认值。real_param_num 是转换函数 _funs() 的第一个参数,用于接收变长参数宏 fun2() 被调用时输入的参数个数。
另外,鉴于视乎实际需求,目标函数的参数也可以是 2 之外的其它数目,所以为了便于扩展转换函数 _funs() 的应用范围,需要输入目标函数的完整参数个数,继续迭代转换函数 _funs()。
scss
#define fun2(...) _funs(2, ARGC(__VA_ARGS__), 123, 456, ##__VA_ARGS__)
void _funs(int param_num_max, int real_param_num, ...)
{
...
// _fun2(val1, val2);
}
用变长参数宏 fun2() 代表有 2 个输入参数的目标函数 _fun2(),param_num_max 指定目标函数的完整参数个数。
ARGC 如何实现?
为什么使用宏定义来计算函数实际输入参数的个数?
由于 C 运行过程中确认参数个数会消耗系统资源,并降低程序运行效率,所以尽量采用在编译预处理时计算的宏定义。
暂定参数个数上限是 2 个,用宏定义预处理对参数的展开来统计参数个数,涉及占位符的妙用(相信会令你开眼界):
scss
#define __ARGS(X) (X)
#define __ARGC_N(_0,_1,N,...) N
#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,2,1))
#define ARGC(...) __ARGC(__VA_ARGS__)
其中 _0, _1,... 是预处理占位符,用于在宏定义展开参数时逐个匹配参数,_0 匹配第一个参数,_1 匹配第二个参数,以此类推,最终 N 用于匹配 __ARGC_N(__VA_ARGS__,2,1)
后边的逆序数字 2 或者 1,这个 N 就是最终计算所得的参数个数。
测试一下对参数个数的计算效果:
scss
printf("ARGC() arg num=%d\n", ARGC());
printf("ARGC(1) arg num=%d\n", ARGC(1));
printf("ARGC(1, 2) arg num=%d\n", ARGC(1, 2));
针对上面的调用,为了更好理解求值过程,让我们尝试一下手动对宏调用 ARGC(1) 展开看看:
scss
ARGC(1)
__ARGC(1)
__ARGS(__ARGC_N(1,2,1))
(__ARGC_N(1,2,1)) ----> (__ARGC_N(1,2,N))
(1) ----> N = 1
ARGC(1) 应该返回 1。
完整测试结果输出:
ini
ARGC() arg num=1
ARGC(1) arg num=1
ARGC(1, 2) arg num=2
为什么 ARGC() 输入 0 个参数时,计算结果不为 0,而输入其它数量的参数计算结果符合预期呢?计算过程还有待改进。
你可以自己手动展开一下 ARGC() 看看,当 ARGC() 输入 0 个参数时,__ARGC_N(__VA_ARGS__,2,1)
后边的逆序数字数量不足以匹配 N,此时的 N 就强制匹配最后一个数字 1 了。为了解决这种异常匹配,当 N == 1 时,可以追加对第一个输入参数是否为空的判断:
scss
#define __ARGC_N(_0,_1,N,...) N==1?(#_0)[0]!=0:N
#_0
用于将第一个参数转换成字符串,会占用一点空间,但不影响程序运行效率。(#_0)[0]
提取字符串的第一个字节。当 N == 1 时,如果第一个参数是空字符,(#_0)[0]!=0
返回 false,否则返回 true。false 转换成 0,true 转换成 1。
最终测试结果输出:
ini
ARGC() arg num=0
ARGC(1) arg num=1
ARGC(1, 2) arg num=2
如果参数上限是 3 个,又怎么修改呢?
scss
#define __ARGS(X) (X)
#define __ARGC_N(_0,_1,_2,N,...) N==1?(#_0)[0]!=0:N
#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,3,2,1))
#define ARGC(...) __ARGC(__VA_ARGS__)
__ARGC_N(__VA_ARGS__,3,2,1)
后边的逆序最大数字 3 就决定了参数的上限个数为 3。当然修改参数上限,__ARGC_N 宏定义输入参数中的占位符 _0,_1,_2
个数需要对应参数上限的个数,占位符从 _0 开始。
如果输入的参数超过了上限呢?
scss
printf("ARGC(1, 2, 3) arg num=%d\n", ARGC(1, 2, 3));
printf("ARGC(1, 2, 3, 6) arg num=%d\n", ARGC(1, 2, 3, 6));
printf("ARGC(1, 2, 3, 6, 9) arg num=%d\n", ARGC(1, 2, 3, 6, 9));
结果输出:
ini
ARGC(1, 2, 3) arg num=3
ARGC(1, 2, 3, 6) arg num=6
ARGC(1, 2, 3, 6, 9) arg num=6
输出的参数个数就等于超出上限的第一个参数值,不过,有言在先,参数个数有上限,而且合理设计的函数参数不应该过多,不必过度计较了。
未完待续,关注我查看更多精彩内容