手把手教你实现 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

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


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

相关推荐
学嵌入式的小杨同学3 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
精彩极了吧4 小时前
C语言基本语法-自定义类型:结构体&联合体&枚举
c语言·开发语言·枚举·结构体·内存对齐·位段·联合
进击的小头6 小时前
实战案例:51单片机低功耗场景下的简易滤波实现
c语言·单片机·算法·51单片机
czy87874757 小时前
const 在 C/C++ 中的全面用法(C/C++ 差异+核心场景+实战示例)
c语言·开发语言·c++
pcm1235679 小时前
设计C/S架构的IM通信软件(4)
java·c语言·架构
代码无bug抓狂人9 小时前
C语言之表达式括号匹配
c语言·开发语言·算法
晓131311 小时前
第二章 【C语言篇:入门】 C 语言基础入门
c语言·算法
jiang_changsheng11 小时前
环境管理工具全景图与深度对比
java·c语言·开发语言·c++·python·r语言
前端玖耀里14 小时前
Linux C/C++ 中系统调用与库函数调用的区别
linux·c语言·c++
进击的小头15 小时前
设计模式与C语言高级特性的结合
c语言·设计模式