• C++ 实现可变参数的三个方法


    有时我们无法提前预知应该向函数传递几个实参。例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一。然而,错误信息的种类不同,所以调用错误输出函数时传递的实参也各不相同。编写变长参数的函数有几种方法:

    C方法:va_list

    stdarg.h头文件提供了C语言中变长参数的功能,但用法较为复杂。

    要使用C方法变长参数,首先在函数声明里用...声明列表(必须在最后,且不能只有...):

    int f(int n, ...);//合法
    int g(int n, ... int m);//不合法
    int h(...)//不合法

    在调用的时候,我们就可以传入变长参数:f(3, 400, 500, 600);

    然后在函数定义里,声明va_list类型的变量(随意取名):va_list myargs;

    然后使用va_start将变长参数拷贝进来,(第二个参数通常是最后一个具名参数,代表长度):va_start(myargs, n);

    接下来,我们可以不断调用va_arg获取下一个参数:

    va_arg(myargs, int); //获取下一个int
    va_arg(myargs, double); //获取下一个double

    最后使用va_end清理结尾:va_end(myarg)

    完整代码:

    #include
    int f(int n,...) {
    va_list myarg;
    va_start(myarg, 10);
    int ans(0);
    for (int i(0);iva_arg(myarg, int); //仅支持int
    va_end(myarg);
    return ans;
    }

    从中就可以看出,这个变长参数列表是直接以获取二进制流,没有包含类型信息,所以做不到处理各种类型。上面的函数只能在传入32位数时工作,传入64位数或者浮点数就会截断成两个数再相加。所以C中的scanf printf两个函数必须传入格式化字符串指定类型,并且不能写错,不然就会有错误的结果。

    C++方法:使用initializer_list

    initializer_list传参则要简单的多,首先这个东西自己就是一种模板容器,使用方法和vector类似。
    image

    不过,它的元素值只作为参数传递,通常不能修改。有了这个东西,我们就可以把多个参数打包成一个参数传入.

    #include
    int max(std::initializer_list<int> li) {
    int ans = 1 << 31;
    for (auto x: li) ans = ans>x ? ans : x;
    return ans;
    }
    main() {
    printf("%d\n", max({1, 2, 3})); //加上大括号,作为整体调用
    }

    优点:很灵活,易于理解。

    缺点:

    • 只支持单一类型
    • 只能读,不能写
    • 要额外加括号,形式不统一

    其实C++11以后标准库就有了变长max了,就是使用的这种括号形式。

    C++方法:使用可变参数模板

    一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包function parameterpacket),表示零个或多个函数参数。

    用下面的方式声明一个可变参数模板:

    template<class T, class... Args> //Args:“模板参数包”
    void foo(const T &t, const Args&... test); //test:“一个参数包(含有0或多个参数)”
    foo(i, s, 42, d); //包中有三个参数
    foo(s, 42, "hi"); //包中有两个参数
    foo(d, s); //包中有一个参数
    foo("hi"); //空包

    如此,编译器会在编译器就实例化所有出现过的版本:

    void foo(const int&,const string&,const int&,const double& );
    void foo(const string&,const int&, const char [3]&);
    void foo(const double&, const string&);
    void foo(const char[3] &);

    模板通常采用递归方式展开:

    template<class T>
    void print(T &t) {cout << t <<'\n';}
    template<class T, class... Args>
    void print(T &t, Args&... rest) {
    cout << t << ' ';
    print(rest...); // 打印剩余参数,注意省略号必须有
    }

    在调用print时,首先调用变长版,然后递归调用print,直到剩下最后一个参数。由于Args也能使用零参数,就与第一个函数形成重载,此时会重载第一个更特化函数,结束递归。所以必须有第一个函数,否则会无限递归。

    优点:

    • 功能最强大,这里只列出入门知识,模板参数包还有非常多的用法。
    • 编译时展开,效率高。

    缺点:

    • 语法复杂繁琐,语义不清晰。省略号的位置我现在都记不清只能现查。更别说进阶语法。
    • 更多是一种炫技。实际应用只需要最基本形式的变长模板就够了。
  • 相关阅读:
    SpringSecurity入门
    【optimtool.unconstrain】无约束优化工具箱
    【Transformer专题】一、Attention is All You Need(Transformer)
    【从入门到起飞】JavaSE—File的使用,构造方法,成员方法
    Spring Cloud 学习笔记(3 3)
    【C++ 结构体的构造函数使用】
    C++异常及异常优缺点
    [hive] posexplode函数
    linux后台运行java项目/ jar包:nohup 命令
    一个UI设计师自学编程的经历
  • 原文地址:https://www.cnblogs.com/ofnoname/p/16524932.html