手把手教你实现 C 语言的函数多参默认值 上

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「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

输出的参数个数就等于超出上限的第一个参数值,不过,有言在先,参数个数有上限,而且合理设计的函数参数不应该过多,不必过度计较了。


未完待续,关注我查看更多精彩内容


相关推荐
天空'之城7 小时前
C 语言工业级通用组件手写 24:互补滤波
c语言·开发语言·互补滤波·嵌入式算法·工业级组件
stevenseahang16 小时前
C语言标准演化史:从K&R到GNU,谁才是正统?
c语言·gnu·可移植性·标准演化·ansic
别动我齐刘海16 小时前
Day6 unitree_G1人形机器人GMR—— MotionInput
c语言·c++·人工智能·学习·机器学习·机器人·github
AI科技星17 小时前
曲率‑挠率与 $\boldsymbol{\omega/c}$ 的关系、精算验证及其物理意义
c语言·开发语言·线性代数·算法·决策树·机器学习·ai科技星
坏柠20 小时前
C语言进阶:从零实现一个可靠的环形缓冲区,吃透回绕、边界与并发
java·c语言·算法
choumin21 小时前
C程序能调用extern “C“声明的带默认参数的C++函数吗?
c语言·c++·extern c·默认参数
LONGZETECH21 小时前
传统实训 vs 仿真实训:从技术架构拆解新能源汽车教学的范式之变
c语言·3d·unity·架构·汽车
白白白小纯21 小时前
每日算法day3—回文链表,链表分割
c语言·数据结构·算法·leetcode
Aurorar0rua21 小时前
CS50 x 2024 Notes Algorithms - 08
c语言·开发语言·算法·学习方法
✎ ﹏梦醒͜ღ҉繁华落℘1 天前
单片机基础知识--LVGL学习-lv_obj_add_flag()
c语言·单片机·学习