Wie kann ich eine Funktion schreiben, die eine variable Anzahl von Argumenten akzeptiert? Ist das möglich, wie?
Wie kann ich eine Funktion schreiben, die eine variable Anzahl von Argumenten akzeptiert? Ist das möglich, wie?
Antworten:
Sie sollten es wahrscheinlich nicht tun, und Sie können wahrscheinlich sicherer und einfacher tun, was Sie möchten. Technisch gesehen schließen Sie stdarg.h ein, um eine variable Anzahl von Argumenten in C zu verwenden. Daraus erhalten Sie den va_list
Typ sowie drei Funktionen, die darauf ausgeführt werden va_start()
, va_arg()
und 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;
}
Wenn Sie mich fragen, ist das ein Chaos. Es sieht schlecht aus, ist unsicher und voller technischer Details, die nichts mit dem zu tun haben, was Sie konzeptionell erreichen möchten. Verwenden Sie stattdessen Überladung oder Vererbung / Polymorphismus, Builder-Muster (wie in operator<<()
Streams) oder Standardargumente usw. Diese sind alle sicherer: Der Compiler erfährt mehr darüber, was Sie versuchen, sodass es mehr Gelegenheiten gibt, die er stoppen kann Sie, bevor Sie Ihr Bein abblasen.
...
Syntax anzugeben ?
printf()
beispielsweise analysiert die Funktion das Zeichenfolgenargument für spezielle Token, um herauszufinden, wie viele zusätzliche Argumente in der Liste der variablen Argumente zu erwarten sind.
<cstdarg>
in C ++ anstelle von<stdarg.h>
In C ++ 11 haben Sie zwei neue Optionen, wie auf der Referenzseite für Variadic-Funktionen im Abschnitt Alternativen angegeben :
- Variadische Vorlagen können auch verwendet werden, um Funktionen zu erstellen, die eine variable Anzahl von Argumenten annehmen. Sie sind häufig die bessere Wahl, da sie die Arten der Argumente nicht einschränken, keine Integral- und Gleitkomma-Promotions durchführen und typsicher sind. (seit C ++ 11)
- Wenn alle Variablenargumente einen gemeinsamen Typ haben, bietet eine std :: initializer_list einen praktischen Mechanismus (wenn auch mit einer anderen Syntax) für den Zugriff auf Variablenargumente.
Unten sehen Sie ein Beispiel, das beide Alternativen zeigt ( live sehen ):
#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 } ) ;
}
Wenn Sie die magische Variable PRETTY_FUNCTION verwenden gcc
oder clang
wir sie verwenden können, um die Typensignatur der Funktion anzuzeigen, kann dies hilfreich sein, um zu verstehen, was vor sich geht. Zum Beispiel mit:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
Die folgenden Ergebnisse würden für verschiedene Funktionen im Beispiel folgen ( siehe live ):
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
In Visual Studio können Sie FUNCSIG verwenden .
Aktualisieren Sie Pre C ++ 11
Pre C ++ 11 die Alternative für std :: initializer_list wäre std :: vector oder einer der anderen Standardcontainer :
#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 ) ;
}
und die Alternative für variadische Vorlagen wären variadische Funktionen, obwohl sie nicht typsicher und im Allgemeinen fehleranfällig sind und unsicher zu verwenden sind, aber die einzige andere mögliche Alternative wäre die Verwendung von Standardargumenten , obwohl dies nur eine begrenzte Verwendung hat. Das folgende Beispiel ist eine modifizierte Version des Beispielcodes in der verknüpften Referenz:
#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 ;
}
Die Verwendung variabler Funktionen unterliegt auch Einschränkungen bei den Argumenten, die Sie übergeben können. Dies wird im Entwurf des C ++ - Standards im Abschnitt 5.2.2
Funktionsaufruf Absatz 7 beschrieben :
Wenn für ein bestimmtes Argument kein Parameter vorhanden ist, wird das Argument so übergeben, dass die empfangende Funktion den Wert des Arguments durch Aufrufen von va_arg (18.7) ermitteln kann. Die Standardkonvertierungen lvalue-to-rvalue (4.1), array-to-pointer (4.2) und function-to-pointer (4.3) werden für den Argumentausdruck ausgeführt. Wenn das Argument nach diesen Konvertierungen keine Arithmetik, Aufzählung, keinen Zeiger, keinen Zeiger auf ein Mitglied oder keinen Klassentyp enthält, ist das Programm fehlerhaft. Wenn das Argument einen Nicht-POD-Klassentyp hat (Klausel 9), ist das Verhalten undefiniert. [...]
typename
vs- class
Nutzung über absichtlich? Wenn ja, bitte erläutern.
initializer_list
rekursiv zu machen ?
Seit der Einführung variabler Vorlagen in C ++ 11 und Fold-Ausdrücken in C ++ 17 ist es möglich, eine Vorlagenfunktion zu definieren, die am Aufruferstandort aufrufbar ist, als wäre sie eine varidische Funktion, aber mit den Vorteilen von ::
Hier ist ein Beispiel für gemischte Argumenttypen
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
Und eine andere mit erzwungener Typübereinstimmung für alle Argumente:
#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!");
^
Mehr Informationen:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
und Tail...
sind gleich ", wobei " sind gleich " bedeutet std::conjunction<std::is_same<Head, Tail>...>
. Lesen Sie diese letzte Definition als " Head
ist das gleiche wie alle Tail...
".
In C ++ 11 können Sie Folgendes tun:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
Listeninitialisierer FTW!
In C ++ 11 gibt es eine Möglichkeit, Vorlagen für variable Argumente zu erstellen, die zu einer wirklich eleganten und typsicheren Möglichkeit führen, Funktionen für variable Argumente zu verwenden. Bjarne selbst gibt ein schönes Beispiel für printf unter Verwendung von Vorlagen mit variablen Argumenten in C ++ 11FAQ .
Persönlich halte ich dies für so elegant, dass ich mich nicht einmal mit einer variablen Argumentfunktion in C ++ beschäftigen würde, bis dieser Compiler C ++ 11-Vorlagen für variable Argumente unterstützt.
,
Operator mit Fold-Ausdrücken verwenden). Ansonsten denke ich nicht.
Variadische Funktionen im C-Stil werden in C ++ unterstützt.
Die meisten C ++ - Bibliotheken verwenden jedoch eine alternative Redewendung, z. B. während die 'c' printf
Funktion variable Argumente verwendet, verwendet das c++ cout
Objekt eine <<
Überladung, die sich mit Typensicherheit und ADTs befasst (möglicherweise auf Kosten der einfachen Implementierung).
std::initializer_lists
... Und dies führt bereits zu einer massiven Komplexität einer einfachen Aufgabe.
Abgesehen von Varargs oder Überladung können Sie Ihre Argumente in einem std :: vector oder anderen Containern (z. B. std :: map) zusammenfassen. Etwas wie das:
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);
Auf diese Weise erhalten Sie Typensicherheit und die logische Bedeutung dieser variadischen Argumente wird offensichtlich.
Sicherlich kann dieser Ansatz Leistungsprobleme haben, aber Sie sollten sich darüber keine Sorgen machen, es sei denn, Sie sind sicher, dass Sie den Preis nicht zahlen können. Es ist eine Art "pythonischer" Ansatz für c ++ ...
Der einzige Weg ist die Verwendung von Variablenargumenten im C-Stil, wie hier beschrieben . Beachten Sie, dass dies keine empfohlene Vorgehensweise ist, da sie nicht typsicher und fehleranfällig ist.
Es gibt keine Standardmethode für C ++, ohne auf varargs ( ...
) im C-Stil zurückzugreifen .
Es gibt natürlich Standardargumente, die je nach Kontext wie eine variable Anzahl von Argumenten "aussehen":
void myfunc( int i = 0, int j = 1, int k = 2 );
// other code...
myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
Alle vier Funktionsaufrufe werden myfunc
mit unterschiedlicher Anzahl von Argumenten aufgerufen . Wenn keine angegeben sind, werden die Standardargumente verwendet. Beachten Sie jedoch, dass Sie nur nachfolgende Argumente weglassen können. Es gibt zum Beispiel keine Möglichkeit, i
nur wegzulassen und zu geben j
.
Möglicherweise möchten Sie Überladung oder Standardparameter - definieren Sie dieselbe Funktion mit Standardparametern:
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 );
}
Auf diese Weise können Sie die Methode mit einem von vier verschiedenen Aufrufen aufrufen:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
... oder Sie suchen nach den v_args-Aufrufkonventionen von C.
Wenn Sie den Bereich der Anzahl der Argumente kennen, die bereitgestellt werden, können Sie immer eine Funktionsüberladung verwenden, z
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
und so weiter...
Beispiel für die Verwendung verschiedener Vorlagen zur Reproduktion console.log
in JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Dateiname zB 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;
}
};
Wie andere gesagt haben, Varargs im C-Stil. Ähnliches können Sie aber auch mit Standardargumenten tun.
Es ist jetzt möglich ... mit boost any und templates In diesem Fall kann der Argumenttyp gemischt werden
#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);
}
Kombinieren Sie die C- und C ++ - Lösungen für die semantisch einfachste, performanteste und dynamischste Option. Wenn Sie es vermasseln, versuchen Sie etwas anderes.
// 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;
}
Benutzer schreibt:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
Semantisch sieht und fühlt sich dies genau wie eine n-Argument-Funktion an. Unter der Haube können Sie es auf die eine oder andere Weise auspacken.
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;
}
}
Einfache Version