• C 和 C++ 可变参数介绍


    前言

    C 和 C++ 可变参数介绍。


    概念

    可变(长)/不定(长)参数:函数可以接收任意数量的参数(函数在声名和定义时不明确参数的数量)


    C 的可变参数

    参数列表 #va_list 4组宏

    头文件

    • #va_list:类型宏;参数列表
    • #va_start():函数宏;va_list 指向参数列表的第一个参数
    • #va_arg():函数宏;依据类型,va_list 指向参数列表的下一个参数
    • #va_end():函数宏;清理 va_list

    底层原理

    • #va_list:字符指针
    • #va_start():指针指向第一个元素
    • #va_arg():指针指向下一个元素
    • #va_end():指针置空

    缺点

    • 代码逻辑需要明确参数的数量和每个参数的类型

    代码示例

    #include  // #va_list、#va_start()、#va_arg()、#va_end()
    #include 
    
    // 形参的一般形式:
    // num:参数数量
    // ...:参数列表
    void print(int num, ...)
    {
        // 1. 定义 va_list
        va_list para_list; // 类型宏;参数列表
    
        // 2. 初始化 va_list
        va_start(para_list, num); // 函数宏;va_list 指向参数列表的第一个参数
    
        // 3. 遍历 va_list
        for (int i = 0; i < num; ++i)
        {
            printf("%d ", va_arg(para_list, int)); // 函数宏;依据类型,va_list 指向参数列表的下一个参数
        }
        printf("\n");
    
        // 4. 清理 va_list
        va_end(para_list); // 函数宏;清理 va_list
    
        return;
    }
    
    int main()
    {
        print(2, 0, 1);
        // 实参的一般形式:
        // 2:参数数量
        // 0 1:参数列表
    
        print(3, 0, 1, 2);
    
        return 0;
    }
    // 输出:
    // 0 1
    // 0 1 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    C++ 的可变参数

    参数列表 #va_list 4组宏

    见 “C 的可变参数” 内容。

    头文件


    初始化列表 initializer_list<> 类模板

    头文件

    原理

    • 类比容器 vector<>
    • 比容器轻量
    • 封装参数(指向参数的指针、参数的数量和参数的类型等)的包装器/对象

    缺点

    • 代码逻辑需要明确参数的类型
    • 一个 initializer_list<> 对象只支持一种类型(可以使用多个 initializer_list<> 对象按序支持多种类型)

    按序:如一个 initializer_list 对象表示一部分参数都是 int 类型,另一个 initializer_list 对象表示另一部分参数都是 string 类型;不能是一个 initializer_list 对象表示一部分参数既有 int 类型又有 string 类型

    代码示例

    // #include  // initializer_list<>
    #include 
    
    using std::cout;
    using std::endl;
    using std::initializer_list;
    
    void print(initializer_list<int> li) // 使用 initializer_list<> 对象接收可变参数
    {
        for (const int l : li)
        {
            cout << l << " ";
        }
        cout << endl;
    
        return;
    }
    
    int main()
    {
        print({0, 1}); // 使用列表初始化创建匿名 initializer_list<> 对象并作为参数
        print({0, 1, 2});
    
        return 0;
    }
    // 输出:
    // 0 1
    // 0 1 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    可变参数模板

    相关语法

    • typename…:定义模板参数包
    • Args:模板参数(抽象概念) 包的名称,可自定义名称,表示任意类型和数量的模板参数
    • Args…:模板参数包
    • args:具体参数(具体概念) 包的名称,可自定义名称,表示任意类型和数量的具体参数
    • args…:展开具体参数包
    • sizeof…(具体参数包):获取具体参数包参数的数量
    • …:折叠表达式

    折叠表达式的概念和语法较复杂 (作者觉得很怪异),在此不深入讲解。
    可参见:(C++模板编程):折叠表达式、可变参表达式_c++模板折叠-CSDN博客

    解包方式

    • 递归展开1
    • 递归展开2(C++ 17支持)
    • 逗号表达式展开1
    • 逗号表达式展开2(优化)
    • 逗号表达式3(优化)
    • 折叠表达式展开(C++ 17支持)

    缺点

    • 概念较复杂
    • 语法较复杂

    获取具体参数包参数的数量

    #include 
    
    using std::cout;
    using std::endl;
    
    template <typename... Args>
    void print(Args... args)
    {
        cout << sizeof...(args) << endl;
    
        return;
    }
    
    int main()
    {
        print(0, 'c'); // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    2
    3
    
    逐行解释:
    2:具体参数包参数的数量是2
    3:具体参数包参数的数量是3
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    递归展开1

    #include 
    
    using std::cout;
    using std::endl;
    
    // 参数数量 == 1的函数模板
    // 递归终止时调用
    template <typename T>
    void print(T value)
    {
        cout << value << endl; // 参数值
    
        return;
    }
    
    // 可变参数模板
    // 参数数量 > 1的函数模板
    // 递归时调用
    template <typename T, typename... Args>
    void print(T value, Args... args)
    {
        cout << value << " "; // 参数值
    
        print(args...); // 递归调用
    
        return;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    递归展开2(C++ 17支持)

    #include 
    
    using std::cout;
    using std::endl;
    
    // 可变参数模板
    // 参数数量 >= 1的函数模板
    template <typename T, typename... Args>
    void print(T value, Args... args)
    {
        cout << value << " "; // 参数值
    
        // 参数数量为0时无法递归调用:print(args...);,需要递归终止
        // C++ 17标准支持“if constexpr()”语法,可以在编译而不是运行时求值以终止递归,使得编译通过
        if constexpr (sizeof...(args) > 0) // 递归调用
        {
            print(args...);
        }
        else // 递归终止
        {
            cout << endl;
        }
    
        return;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    逗号表达式展开1

    #include 
    // #include   // initializer_list<>
    
    using std::cout;
    using std::endl;
    using std::initializer_list;
    
    // 可变参数模板
    // 参数数量 >= 1的函数模板
    template <typename T, typename... Args>
    void print(T value, Args... args)
    {
        cout << value << " "; // 第一个参数值
    
        // 重点理解:
        // [args]{cout << args << " ";}:Lambda 表达式
        // [args]{cout << args << " ";}():调用 Lambda 表达式
    
        // value:第一个参数的值
        // (,):逗号表达式:先计算左表达式,再计算右表达式,结果是右表达式的值
        // ([args]{cout << args << " ";}(), value):先调用 Lambda 表达式,再计算第一个参数的值,结果是第一个参数的值
    
        // args...:展开具体参数包
        // ([args]{cout << args << " ";}(), value)...:展开具体参数包,对每一个参数,先调用 Lambda 表达式,再计算第一个参数值,结果是第一个参数值
    
        // typename T:第一个参数的类型
        // initializer_list<>{}:initializer_list<> 对象
        // initializer_list{}:匿名 initializer_list<> 对象,值类型是第一个参数的类型
        // initializer_list{([args]{cout << args << " ";}(), value)...};:第一个参数作为匿名 initializer_list<> 对象的值,值类型是第一个参数的类型
    
        // C++11 和 C++14 标准,没有提供一种直接将具体参数包展开到函数调用参数列表中的语法
        // 所以可以使用 initializer_list<> 结合 args... 展开具体参数包
        // 又因为 initializer_list<> 的值需要相同类型
        // 所以可以使用逗号表达式,无论左表达式怎么计算,都返回第一个参数的类型和值 T value
        // 所以函数模板需要定义 typename T,函数需要定义 T value
        initializer_list<T>{([args]
                             { cout << args << " "; }(),
                             value)...};
        cout << endl;
    
        return;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    逗号表达式展开2(优化)

    #include 
    // #include   // initializer_list<>
    
    using std::cout;
    using std::endl;
    using std::initializer_list;
    
    // 可变参数模板
    // 参数数量 >= 1的函数模板
    // 依据“逗号表达式展开1”的分析,模板参数 typename T、初始化列表 initializer_list<> 的类型 T、第一个参数值 value 和逗号表达式的右表达式 value 有意义但无用途,可以优化
    template <typename... Args>
    void print(Args... args)
    {
        initializer_list<int>{([args]
                               { cout << args << " "; }(),
                               0)...};
        cout << endl;
    
        return;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    逗号表达式展开3(优化)

    #include 
    #include 
    
    using std::cout;
    using std::endl;
    using std::vector;
    
    // 可变参数模板
    // 参数数量 >= 1的函数模板
    // 依据“逗号表达式展开1”的分析,对于可以使用列表初始化 {} 的对象,数组和向量 vector<> 等,可以结合 args... 展开具体参数包
    template <typename... Args>
    void print(Args... args)
    {
        int arr[]{([args]
                   { cout << args << " "; }(),
                   0)...};
        cout << endl;
    
        vector<int>{([args]
                     { cout << args << " "; }(),
                     0)...};
        cout << endl;
    
        return;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c
    0 c str
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    折叠表达式展开(C++ 17支持)

    #include 
    
    using std::cout;
    using std::endl;
    
    // 可变参数模板
    // 参数数量 >= 1的函数模板
    template <typename... Args>
    void print(Args... args)
    {
        // 二元左折叠表达式(概念复杂)
        // (,):逗号表达式:连接折叠表达式和操作
        // 对每一个参数,先输出参数,再输出空格
        (..., (cout << args << ' '));
        cout << endl;
    }
    
    int main()
    {
        print(0, 'c');        // 2个不同类型的参数
        print(0, 'c', "str"); // 3个不同类型的参数
    
        return 0;
    }
    /*
    输出:
    0 c
    0 c str
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    总结

    C 和 C++ 可变参数介绍。


    参考资料


    作者的话

    • 感谢参考资料的作者/博主
    • 作者:夜悊
    • 版权所有,转载请注明出处,谢谢~
    • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
    • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
    • 文章在认识上有错误的地方, 敬请批评指正
    • 望读者们都能有所收获

  • 相关阅读:
    ImGUI 1.87 绘制D3D外部菜单
    Spring 九大事务失效场景分析
    【零基础学QT】第六章 定时器QTimer 控制LED闪烁
    C语言 ,不用string.h的函数,实现A+B A-B的字符串处理功能。
    centos磁盘管理 删除文件后还是会占用空间
    2022柏林葡萄酒大奖赛 | 瑞格尔侯爵佳酿斩获两枚金奖
    SSM SpringBoot vue学校办公自动化系统
    Java注解(Annotation)与元注解
    [附源码]计算机毕业设计JAVA乒乓球俱乐部管理系统
    JAVA计算机毕业设计电子病历系统Mybatis+系统+数据库+调试部署
  • 原文地址:https://blog.csdn.net/m0_62083249/article/details/134260171