c++中参数的变量数?

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

468006 次浏览

唯一的方法是使用C样式变量参数,如在这里所述。请注意,这不是一个推荐的实践,因为它不类型安全且容易出错。

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

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

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

你可能不应该这样做,你可以用一种更安全、更简单的方式做你想做的事情。从技术上讲,要在C语言中使用可变数量的参数,需要包含stdarg.h。由此,你将得到va_list类型以及对其进行操作的三个函数va_start()va_arg()va_end()

#include<stdarg.h>


int maxof(int n_args, ...)
{
va_list ap;
va_start(ap, n_args);
int max = va_arg(ap, int);
for(int i = 2; i <= n_args; i++) {
int a = va_arg(ap, int);
if(a > max) max = a;
}
va_end(ap);
return max;
}

要我说,这真是一团糟。它看起来很糟糕,不安全,而且充满了与你在概念上试图实现的目标无关的技术细节。相反,可以考虑使用重载或继承/多态性、构建器模式(如流中的operator<<())或默认参数等。这些都是更安全的:编译器会更多地了解你要做什么,这样就有更多的机会在你断腿之前阻止你。

如果不使用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

除了可变参数或重载,你可以考虑将参数聚合在std::vector或其他容器中(例如std::map)。就像这样:

template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);

通过这种方式,您将获得类型安全,并且这些可变参数的逻辑含义将是显而易见的。

当然,这种方法可能会有性能问题,但您不必担心,除非您确定无法为此付出代价。它是c++的一种“Pythonic”方法…

在c++ 11中,有一种方法可以创建变量参数模板,从而以一种非常优雅且类型安全的方式来使用变量参数函数。Bjarne自己在c++ 11 faq中给出了一个使用变量参数模板打印的很好的例子。

就我个人而言,我认为这是如此优雅,以至于在编译器支持c++ 11变量参数模板之前,我甚至不会为c++中的变量参数函数而烦恼。

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

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调用约定。

如果所有实参都是const且类型相同,也可以使用initializer_list

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

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

下面的例子显示了两个选项(see it live):

#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 } ) ;
}

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

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

对于示例(see it live)中的可变变量函数,将产生如下int结果:

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::向量或其他标准集装箱之一:

#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 ;
}

使用可变的函数在参数中也有限制,可以在5.2.2函数调用7中详细说明:

当给定的实参没有形参时,实参以这样一种方式传递,即接收函数可以通过调用va_arg(18.7)获得实参的值。左值到右值(4.1)、数组到指针(4.2)和函数到指针(4.3)的标准转换在实参表达式上执行。在这些转换之后,如果实参没有算术、枚举、指针、指向成员的指针或类类型,则程序是格式错误的。如果实参具有非pod类类型(子句9),则行为未定义。[…]

在c++11中,你可以做:

void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}


foo({"arg1","arg2"});

列表初始化项FTW!

int fun(int n_args, ...) {
int *p = &n_args;
int s = sizeof(int);
p += s + s - 1;
for(int i = 0; i < n_args; i++) {
printf("A1 %d!\n", *p);
p += 2;
}
}

普通的版本

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!");
^

更多信息:

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

使用可变参数模板,示例重现JavaScript中所见的console.log:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

文件名,例如js_console.h:

#include <iostream>
#include <utility>


class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}


template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}


template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};
现在有可能…使用boost任意和模板 在这种情况下,参数类型可以混合

#include <boost/any.hpp>
#include <iostream>


#include <vector>
using boost::any_cast;


template <typename T, typename... Types>
void Alert(T var1,Types... var2)
{


std::vector<boost::any> a(  {var1,var2...});


for (int i = 0; i < a.size();i++)
{


if (a[i].type() == typeid(int))
{
std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
}
if (a[i].type() == typeid(double))
{
std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
}
if (a[i].type() == typeid(const char*))
{
std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
}
// etc
}


}




void main()
{
Alert("something",0,0,0.3);
}
// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
T * arr = new T[n];
va_list ap;
va_start(ap, n);
for (size_t i = 0; i < n; i++)
T[i] = va_arg(ap,T);
return arr;
}


用户写道:

auto arr = spawn<float> (3, 0.1,0.2,0.3);

从语义上讲,这看起来和感觉上完全像一个n参数函数。在引擎盖下,你可能会以这样或那样的方式打开它。

支持彩色代码的c++ 11

  • 是通用的,适用于所有数据类型
  • 类似JavaScript console.log(1,"23")
  • 支持颜色代码的信息,警告,错误。
  • <李>例子:
    enter image description here < / >
#pragma once
#include <iostream>
#include <string>


const std::string RED = "\e[0;91m";
const std::string BLUE = "\e[0;96m";
const std::string YELLOW = "\e[0;93m";


class Logger {
private:
enum class Severity { INFO, WARN, ERROR };


static void print_colored(const char *log, Severity severity) {
const char *color_code = nullptr;


switch (severity) {
case Severity::INFO:
color_code = BLUE.c_str();
break;
case Severity::WARN:
color_code = YELLOW.c_str();
break;
case Severity::ERROR:
color_code = RED.c_str();
break;
}


std::cout << "\033" << color_code << log << "\033[0m -- ";
}


template <class Args> static void print_args(Args args) {
std::cout << args << " ";
}


public:
template <class... Args> static void info(Args &&...args) {
print_colored("[INFO] ", Severity::INFO);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}


template <class... Args> static void warn(Args &&...args) {
print_colored("[WARN] ", Severity::WARN);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}


template <class... Args> static void error(Args &&...args) {
print_colored("[ERROR]", Severity::ERROR);
int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
std::cout << std::endl;
}
};