我怎么能写一个函数,接受可变数量的参数?这可能吗?怎么可能?


当前回答

可能你想重载或默认参数-用默认参数定义相同的函数:

void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
   // stuff
}

void doStuff( double std_termstator )
{
   // assume the user always wants '1' for the a param
   return doStuff( 1, std_termstator );
}

这将允许你用四种不同的调用之一来调用该方法:

doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );

…或者你可以从C中寻找v_args调用约定。

其他回答

c++ 17解决方案:完全类型安全+良好的调用语法

由于在c++ 11中引入了变进模板,在c++ 17中引入了fold表达式,因此可以在调用端定义一个模板函数,它可以像一个变进函数一样被调用,但优点是:

强类型安全; 在不使用参数数量的运行时信息或不使用“stop”参数的情况下工作。

下面是一个混合参数类型的例子

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

另一个对所有参数强制类型匹配:

#include <type_traits> // enable_if, conjuction

template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;

template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!");   // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                               // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                              ^

更多信息:

可变参数模板,也称为参数包(自c++ 11起)- cppreference.com。 折叠表达式(自c++ 17开始) 查看coliru上的完整程序演示。

c++支持C风格的变进函数。

然而,大多数c++库使用另一种习惯,例如,' C ' printf函数接受变量参数,而c++ cout对象使用<<重载来解决类型安全和adt(可能以实现简单性为代价)。

如果不使用C风格的可变参数(…),就没有标准的c++方法可以做到这一点。

当然,根据上下文,有一些默认参数“看起来”像可变数量的参数:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

这四个函数调用都使用不同数量的参数调用myfunc。如果没有给出参数,则使用默认参数。但是请注意,只能省略尾随参数。没有办法,例如省略i而只给出j。

正如其他人所说,c风格的变量。但是你也可以对默认参数做类似的事情。

在c++ 11中,你有两个新的选项,正如可变参数参考页中alternative部分所述:

可变参数模板也可用于创建具有可变数量的参数的函数 参数。它们通常是更好的选择,因为它们不施加限制 参数的类型,不执行整数和浮点提升,以及 类型安全。(因为c++ 11) 如果所有变量参数共享一个公共类型,std::initializer_list将提供一个 访问变量参数的便利机制(尽管语法不同)。

下面是一个展示这两种选择的例子(看现场):

#include <iostream>
#include <string>
#include <initializer_list>

template <typename T>
void func(T t) 
{
    std::cout << t << std::endl ;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    std::cout << t <<std::endl ;

    func(args...) ;
}

template <class T>
void func2( std::initializer_list<T> list )
{
    for( auto elem : list )
    {
        std::cout << elem << std::endl ;
    }
}

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    func(1,2.5,'a',str1);

    func2( {10, 20, 30, 40 }) ;
    func2( {str1, str2 } ) ;
} 

如果您正在使用gcc或clang,我们可以使用PRETTY_FUNCTION魔术变量来显示函数的类型签名,这有助于理解正在发生的事情。例如:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

对于例子中的可变函数,结果如下(见现场):

void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello

在Visual Studio中,您可以使用FUNCSIG。

更新Pre c++ 11

在c++ 11之前,std::initializer_list的替代方法是std::vector或其他标准容器之一:

#include <iostream>
#include <string>
#include <vector>

template <class T>
void func1( std::vector<T> vec )
{
    for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
    {
        std::cout << *iter << std::endl ;
    }
}

int main()
{
    int arr1[] = {10, 20, 30, 40} ;
    std::string arr2[] = { "hello", "world" } ; 
    std::vector<int> v1( arr1, arr1+4 ) ;
    std::vector<std::string> v2( arr2, arr2+2 ) ;

    func1( v1 ) ;
    func1( v2 ) ;
}

可变变量模板的替代方法是可变变量函数,尽管它们不是类型安全的,而且通常容易出错,使用起来不安全,但唯一的其他潜在替代方法是使用默认参数,尽管它的用途有限。下面的例子是链接参考中样例代码的修改版本:

#include <iostream>
#include <string>
#include <cstdarg>
 
void simple_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
 
    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '\n';
        }
        ++fmt;
    }
 
    va_end(args);
}
 

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    simple_printf("dddd", 10, 20, 30, 40 );
    simple_printf("ss", str1.c_str(), str2.c_str() ); 

    return 0 ;
} 

使用可变值函数也会在参数中带来限制,这在c++标准草案5.2.2节函数调用第7段中有详细说明:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7). The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. [...]