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

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


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

相关推荐
李白同学4 小时前
【C语言】结构体内存对齐问题
c语言·开发语言
楼台的春风6 小时前
【MCU驱动开发概述】
c语言·驱动开发·单片机·嵌入式硬件·mcu·自动驾驶·嵌入式
waicsdn_haha15 小时前
Visual Studio Code 2025 安装与高效配置教程
c语言·ide·windows·vscode·微软·编辑器·win7
----云烟----15 小时前
C/C++ 中 volatile 关键字详解
c语言·开发语言·c++
BanLul16 小时前
进程与线程 (三)——线程间通信
c语言·开发语言·算法
落羽的落羽16 小时前
【落羽的落羽 数据结构篇】栈和队列
c语言·数据结构
谏君之17 小时前
C语言实现的常见算法示例
c语言·算法·排序算法
曾浩轩18 小时前
51单片机学习之旅——C语言小知识
c语言·学习·51单片机
Want59519 小时前
C/C++跳动的爱心
c语言·开发语言·c++
kongba00719 小时前
Cursor提示词模板,开发GD32,C语言开发GD32 ARM单片机编程规范提示词 大厂风格代码规范
c语言·arm开发·单片机