当前位置:   article > 正文

C++ 可变参数_c++可变参数

c++可变参数

前言

在C语言中,printf是一个我们常用的函数,我们可以无限传入不同类型的参数,这里就用到了可变参数。我们一般使用形参包...或者是std::initializer_list来实现可变参数。不仅函数可以有可变参数,模板也可以有。因此形参包一般可以分为模板形参包和函数形参包。

形参包

函数形参包

声明

形参包的声明就是英文省略号,即三个点(...)。比如如下声明就是没问题的,在C和C++中都可以通过编译:

void print(const char* fmt, ...);
  • 1

如果是C++的话,可以省略前面那个逗号。下面的声明只能在C++中编译通过,C语言会报错。所以为了与C的兼容性,还是建议使用第一个声明。

void print(const char* fmt...);
  • 1

要注意的是,形参包只能放在形参列表的末尾。下面的声明就是错误的

void print(..., const char* fmt); //incorrect
  • 1

形参列表只包含形参包是合法的,但是我们后续就无法访问实参,原因下文会讲到。

void print(...); //correct, but the params could not be used
  • 1

访问实参

在 <stdarg.h> 中提供了va_listva_startva_argva_copyva_end这一个类型和四个宏函数来实现实参的访问:

  • va_list:一个类型,保有 va_start、va_arg、va_end 和 va_copy所需的信息
  • va_start:启用对可变函数实参的访问
  • va_arg:访问下一个可变函数实参
  • va_copy:制造可变函数实参的副本
  • va_end:结束对可变函数实参的遍历

在访问之前声明一个va_list类型的变量,比如args

void func(int count,...){
    va_list args;
}
  • 1
  • 2
  • 3

之后调用va_start初始化,第一个参数传入args,第二个传入形参包前一个参数,这里是count,然后在最后调用va_end停止遍历,这就是刚才我们说的如果只在参数列表中声明...不能访问的原因:

void func(int count,...){
    va_list args;
    va_start(args, count);
    // do something here
    va_end(args);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

va_copy可以拷贝一个va_list到另一个va_list:

    std::va_list args1;
    va_start(args1, count);
    std::va_list args2;
    va_copy(args2, args1);
  • 1
  • 2
  • 3
  • 4

va_arg 需要两个参数,第一个是va_list类型,比如刚才的args,然后是需要获取的类型。每调用一次,都会修改args使其指向下一个参数。如果下个参数没有访问则是默认值,比如int类型就是0。下面有个加法函数的例子:

#include <iostream>
#include <cstdarg>

int add(int count, ...){
    int sum = 0;
    std::va_list args;
    va_start(args, count);
    for (int i = 0; i < count; i++) {
        int num = va_arg(args, int);
        sum += num;
    }
    va_end(args);
    return sum;

}

int main(){
    std::cout << add(5, 62 , 13, 7, 45, 11) << '\n';
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

输出内容为:

138
  • 1

可变参数相关C风格IO函数

我们以std::vsprintf为例,剩下的还有std::vprintfstd::vfprintfstd::vsnprintf等等,具体可以参考C-style file input/output - cppreference.com。vsprintf是一个把变参格式化并打印到字符串的函数,这样我们可以自己写一个print函数:

#include <iostream>
#include <cstdarg>

void print(const char* fmt, ...){
    char buf[1024];
    std::va_list args;
    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);

    printf("%s",buf);
}

int main(){
    print("My name is %s. And I'm %d years old.","Chris",17);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

输出内容如下:

My name is Chris. And I'm 17 years old.
  • 1

在宏函数中应用可变参数

我们依旧使用形参包...传参,但是我们要使用__VA_ARGS__访问,并且需要传入另一个函数。这时我们就可以直接只在参数列表中声明...,比如:

#define log(...) print(__VA_ARGS__)
void print(const char* fmt, ...){
    char buf[1024];
    std::va_list args;
    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);

    printf("%s",buf);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

模板形参包

声明

声明如下:

template <typename T, typename ... Args>
void func(T t,Args ... args);
  • 1
  • 2

包展开

template<class... Us>
void f(Us... pargs) {}
 
template<class... Ts>
void g(Ts... args)
{
    f(&args...); // “&args...” 是包展开
                 // “&args” 是它的模式
}
 
g(1, 0.2, "a"); // Ts... args 会展开成 int E1, double E2, const char* E3
                // &args... 会展开成 &E1, &E2, &E3
                // Us... 会展开成 int* E1, double* E2, const char** E3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如果两个形参包在同一模式中出现,那么它们同时展开而且长度必须相同:

template<typename...>
struct Tuple {};
 
template<typename T1, typename T2>
struct Pair {};
 
template<class... Args1>
struct zip
{
    template<class... Args2>
    struct with
    {
        typedef Tuple<Pair<Args1, Args2>...> type;
        // Pair<Args1, Args2>... 是包展开
        // Pair<Args1, Args2> 是模式
    };
};
 
typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
// Pair<Args1, Args2>... 会展开成
// Pair<short, unsigned short>, Pair<int, unsigned int> 
// T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>>
 
typedef zip<short>::with<unsigned short, unsigned>::type T2;
// 错误:包展开中的形参包包含不同长度
  • 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

如果包展开内嵌于另一个包展开中,那么它所展开的是在最内层包展开出现的形参包,并且在外围(而非最内层)的包展开中必须提及其它形参包:

template<class... Args>
void g(Args... args)
{
    f(const_cast<const Args*>(&args)...); 
    // const_cast<const Args*>(&args) 是模式,它同时展开两个包(Args 与 args)
 
    f(h(args...) + args...); // 嵌套包展开:
    // 内层包展开是 “args...”,它首先展开
    // 外层包展开是 h(E1, E2, E3) + args 它其次被展开
    // (成为 h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

获取参数数量

#include <iostream>
using namespace std;

template<typename... Args>
int getArgNums(Args... args){
    return sizeof...(args);
}

int main(){
    cout<<getArgNums("Hello World",666,'c');
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

结果输出如下:

3
  • 1

使用递归的方式遍历

可变参数可以使用递归的方式进行遍历,每次从可变参数中取出第一个元素,然后向下一个参数推导,直到包为空。比如:

#include <iostream>
using namespace std;

template<typename T>
void print(T arg){
    cout << arg << endl;
}

template<typename T, typename... Args>
void print(const T &arg, Args... args){
    cout << arg << endl;
    print(args...);
}

int main(){
    print("Hello World",666,'c');
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

结果输出如下:

Hello World
666
c
  • 1
  • 2
  • 3

std::initializer_list

C++另外提供了initializer_list来实现变参。不过传参必须使用花括号括起来,而且只能是单一类型,然后可以使用迭代器访问:

#include <iostream>
#include <string>
using namespace std;

void print(initializer_list<string> ls) {
    for (auto i = ls.begin(); i != ls.end(); ++i)
        cout << *i << " ";
    cout << endl;
}

int main(){
    print({"Hello World","lalala","cpp"});
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

输出如下:

Hello World lalala cpp
  • 1

参考:cppreference.com

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/577007
推荐阅读
相关标签
  

闽ICP备14008679号