C++中的可变参数使用总结


问题

当调用一个函数时,会遇到以下问题:

  • 提前不知道传入的参数的数量;
  • 也不知道传入参数的类型;

这种问题,在日志记录中最为明显:

  • 记录的日志的信息可能有多条;
  • 记录的日志的数据类型也可以是不同的;

实践解决方式

针对这些问题,在实践中,我一般都是封装为一个数据结构,在Java中使用JavaBeans。在C++中,也可以这么干,但是C++也提供了一些标准的操作方式,具体有以下几种:

  1. ellipsis …
  2. initializer_list
  3. variadic template

C++标准提供的解决方式

ellipsis …

在C++中,通过ellipsis实现可变参数。

  • 这种方式是用于与C语言的代码进行交互,除此之外,不应该在C++语言中使用这种方式。
  • 具体的实现方式通过C语言中的varargs完成;
  • 在函数声明中,可变参数只能是最后一个parameter,即如下两种形式之一:
    void foo(param_list, ...){}
    void foo(...){}
    
  • 另外,不同于基本的函数参数,对于传入函数的可变参数,不会进行类型检查;

initializer_list

特点

这种形式可以表示一组特定类型的参数,这些参数的类型唯一。

  • 可以将initializer_list类比于vector<T>,也是一种template type,因此在定义该类型的变量时,需要指定类型,如下:

    initializer_list<string> ls;
    
  • 但是,与vector不同的是,initializer_list中的值均为const,即无法修改;

    void func(initializer_list<int> il){
        // 1. error, 返回的是pointer to const int
        int *p = il.begin();
        // 2. ok
        const int *p = il.begin();
        
        for(auto &a: il){
            cout << a << endl;
        }
        cout << s << endl;
    }
    

    当用1处的代码初始化 *p时,会报以下的错误:

    error: invalid conversion from ‘std::initializer_list::const_iterator’ {aka ‘const int’} to ‘int

    使用2处的代码则ok。同样的,我们说initializer_list中的数据为const,当使用for循环遍历时,使用如下代码,没有问题。其中的问题来自于auto关键字,参考这篇文章

    for(auto a: il){
        a = 100;
        cout << a << endl;
    }
    

    主要原因在于,这里发生了copy,即出现auto a = il中的每一项,虽然il中的数据为const,但是为top level const,在使用auto时,top level const被忽略。

    但是如下的代码:

    for(auto &a: il){
        a = 100;
        cout << a << endl;
    }
    

    由于auto中用到了reference,此时top level const不会忽略,因此出现如下错误:

    error: assignment of read-only reference ‘a’.

  • 使用initializer_list参数的函数也可以定义其他的parameters,而且不要求initializer_list参数是最后一个;

    void func(initializer_list<int> il, string s){
        for(auto a: il){
            cout << a << endl;
        }
        cout << s << endl;
    }
    
    initializer_list<int> il = {1 ,5 ,9};
    func(il, "hello world");
    

相关操作

关于initializer_list的操作,与vector、数组有点类似,如下:
关于initializer_list的操作

其中,可以利用这些API对initializer_list进行遍历:

  • 基于for循环;
  • 基于begin/end的指针操作;
void func(initializer_list<string> il) {
    for (auto beg = il.begin(); beg != il.end(); ++beg){
        cout << *beg << " " ;
    }
}

variadic template

这部分是模板和泛型编程中的内容,C++ Primer - 16.4 Variadic Templates中有详细地介绍,鉴于还没学到模版编程,这里就先提一嘴,后续遇到后再展开。

参考资料

  1. C++ Primer - 6.2.6 Functions with Varying Parameters

文章作者: alex Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 alex Li !
 上一篇
下一篇 
C++中的函数参数传递 C++中的函数参数传递
总结C++中的函数参数传递方式,包括按值传递、按引用传递,以及指针扮演的角色。
2022-11-02
  目录