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

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/ifnDcV7AK...

本文上接《手把手教你实现 C 语言的函数多参默认值 上》,下文提及的一些概念来源于上文,为方便阅读理解本文内容,建议先了解一下上文内容。

填充默认值

实际使用变长参数宏 fun2() 时,由于参数具有默认值,部分甚至全部参数可以不输入,输入的参数个数范围介于 0 到目标函数 _fun2() 的完整参数个数之间。已输入的参数和默认值被传入转换函数 _funs(),转换函数 _funs() 再从变长参数列表逐个提取,最终未输入的参数会被默认值替代。

假设函数参数数据类型为 val_type,变长参数函数的参数提取一般形式类似如下:

ini 复制代码
va_list valist;
va_start(valist, last_solid_arg);
val_type arg = va_arg(valist, val_type);
va_end(valist);

last_solid_arg 应该是上面代码块所属的函数的最后一个固定参数,否则编译时可能会报警:

warning: second parameter of 'va_start' not last named argument

其实编译器能够自动识别哪个是函数的最后一个固定参数,之所以还要报警,是为了兼容标准,了解这点就可以了。

如果多个参数之间的类型不同,提取参数的步骤会更为复杂。为了简化参数提取,这里统一类型,目标函数 _fun2() 输入参数的类型统一为一种,暂定为 int 类型并且用宏定义表示。

我们看看基于上面的思路实现的转换函数 _funs():

scss 复制代码
#define FUNN_PARAM_TYPE     int

void _funs(int param_num_max, int real_param_num, ...)
{
    va_list valist;

    // 目标函数的参数列表
    FUNN_PARAM_TYPE *args = (FUNN_PARAM_TYPE *)malloc(param_num_max * sizeof(FUNN_PARAM_TYPE));
    if (NULL == args) {
        printf("malloc for args failure\n");
        return;
    }

    // 默认值列表
    FUNN_PARAM_TYPE *defaults = (FUNN_PARAM_TYPE *)malloc(param_num_max * sizeof(FUNN_PARAM_TYPE));
    if (NULL == defaults) {
        printf("malloc for defaults failure\n");
        return;
    }

    printf("real_param_num=%d\n", real_param_num);
    

    va_start(valist, real_param_num); // 第二个参数应该是所在函数的最后一个固定参数
    for (int i = 0; i < 2 * param_num_max; ++ i) {
        if (i < param_num_max) {
            // 提取默认值
            defaults[i] = va_arg(valist, FUNN_PARAM_TYPE);
        } else {
            if ((real_param_num + param_num_max) > i) {
                // 用实际输入参数填充目标函数的参数
                args[i - param_num_max] = va_arg(valist, FUNN_PARAM_TYPE);
            } else {
                // 无输入的参数用默认值填充
                args[i - param_num_max] = defaults[i - param_num_max];
            }
        }
    }
    va_end(valist);

    // 调用目标函数
    switch (param_num_max)
    {
    case 2:
        _fun2(args[0], args[1]);
        break;
    
    default:
        break;
    }

    free(args);
    free(defaults);
}

来测试一下上面的成果,调用宏 fun2() 分别输入 0 个、1 个、2 个参数:

scss 复制代码
printf("fun2()\r\n");
fun2();
printf("---\r\n");
printf("fun2(7)\r\n");
fun2(7);
printf("---\r\n");
printf("fun2(4, 6)\r\n");
fun2(4, 6);

输出结果:

yaml 复制代码
fun2()
real_param_num=0
fun inputs val1:123, val2:456
---
fun2(7)
real_param_num=1
fun inputs val1:7, val2:456
---
fun2(4, 6)
real_param_num=2
fun inputs val1:4, val2:6

好了,多参数默认值功能已经实现了,延申一下:上面的代码能不能兼容单参数函数的默认值呢?

我觉得 OK,同样地,先定义一个单参数的目标函数 _fun1():

arduino 复制代码
void _fun1(int val)
{
    printf("fun inputs val:%d\n", val);
}

仿照变长参数宏 fun2(),对应定义目标函数 _fun1() 的变长参数宏 fun1():

scss 复制代码
#define fun1(...)           _funs(1, ARGC(__VA_ARGS__), 5, ##__VA_ARGS__)

设定目标函数 _fun1() 的参数默认值为 5。

然后再稍微完善一下转换函数 _funs() 的调用目标函数代码块:

ini 复制代码
...
switch (param_num_max)
{
case 1:
    _fun1(args[0]);
    break;
case 2:
    _fun2(args[0], args[1]);
    break;

default:
    break;
}
...

再测试一下上面的成果,调用宏 fun1() 分别输入 0 个、1 个参数:

scss 复制代码
printf("fun1()\r\n");
fun1();
printf("---\r\n");
printf("fun1(2)\r\n");
fun1(2);

输出结果:

kotlin 复制代码
fun1()
real_param_num=0
fun inputs val:5
---
fun1(2)
real_param_num=1
fun inputs val:2

真的实现了"鸡刀宰牛",有点嗨...


有句话叫"什么对你提出了限制,什么就是你前进的绊脚石",这样看的话,上面假设的限制条件中限制了参数类型为同一种,是本文未解决的问题,相信聪明的你已经有眉目了。八戒迫切想要和你一起探讨接下来的问题,欢迎联系我!

相关推荐
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
EricWang13583 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
我是谁??3 小时前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
希言JY4 小时前
C字符串 | 字符串处理函数 | 使用 | 原理 | 实现
c语言·开发语言
午言若4 小时前
C语言比较两个字符串是否相同
c语言
TeYiToKu5 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
互联网打工人no16 小时前
每日一题——第一百二十四题
c语言
爱吃生蚝的于勒6 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~6 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
洋2406 小时前
C语言常用标准库函数
c语言·开发语言