Как я могу написать функцию, которая принимает переменное число аргументов? Это возможно, как?
Как я могу написать функцию, которая принимает переменное число аргументов? Это возможно, как?
Ответы:
Вы, вероятно, не должны, и вы, вероятно, можете делать то, что хотите, более безопасным и простым способом. Технически, чтобы использовать переменное число аргументов в 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<<()
потоках) или аргументов по умолчанию и т. Д. Все они безопаснее: компилятор узнает больше о том, что вы пытаетесь сделать, поэтому есть больше случаев, когда он может остановиться Вы, прежде чем оторвать ногу.
...
синтаксисом?
printf()
, например, функция анализирует строковый аргумент для специальных токенов, чтобы выяснить, сколько дополнительных аргументов следует ожидать в списке переменных аргументов.
<cstdarg>
в C ++ вместо<stdarg.h>
В C ++ 11 у вас есть две новые опции, как указано в справочной странице функций Variadic в разделе « Альтернативы» :
- Шаблоны Variadic также можно использовать для создания функций, которые принимают переменное число аргументов. Часто они являются лучшим выбором, потому что они не налагают ограничений на типы аргументов, не выполняют целочисленное продвижение и продвижение с плавающей запятой и безопасны для типов. (начиная с 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 ;
приведет к следующему 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 .
Обновление до 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 :
Когда для данного аргумента нет параметра, аргумент передается таким образом, что принимающая функция может получить значение аргумента, вызвав va_arg (18.7). Стандартные преобразования lvalue-to-rvalue (4.1), array-to-pointer (4.2) и function-to-pointer (4.3) выполняются в выражении аргумента. После этих преобразований, если аргумент не имеет арифметику, перечисление, указатель, указатель на член или тип класса, программа становится некорректной. Если аргумент имеет тип класса не POD (раздел 9), поведение не определено. [...]
typename
против class
использования выше преднамеренным? Если так, пожалуйста, объясните.
initializer_list
рекурсив?
Поскольку введение шаблонов переменных в C ++ 11 и выражений сворачивания в C ++ 17, можно определить функцию-шаблон, которая на сайте вызывающей стороны может вызываться так, как если бы она была функцией Varidic, но с преимуществами для :
Вот пример для смешанных типов аргументов
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!");
^
Больше информации:
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
Head
и Tail...
одинаковы », где « то же самое » означает std::conjunction<std::is_same<Head, Tail>...>
. Прочитайте это последнее определение как « Head
такое же, как все Tail...
».
в C ++ 11 вы можете сделать:
void foo(const std::list<std::string> & myArguments) {
//do whatever you want, with all the convenience of lists
}
foo({"arg1","arg2"});
инициализатор списка FTW!
В C ++ 11 есть способ создания шаблонов переменных аргументов, который приводит к действительно элегантному и безопасному типу способу иметь функции переменных аргументов. Сам Бьярне приводит хороший пример printf с использованием шаблонов переменных аргументов в C ++ 11FAQ .
Лично я считаю это настолько элегантным, что даже не стал бы беспокоиться о функции с переменным аргументом в C ++, пока этот компилятор не будет поддерживать шаблоны переменных C ++ 11.
,
оператор с выражениями свертывания). Иначе я так не думаю.
В C ++ поддерживаются функции с переменным числом символов.
Однако большинство библиотек C ++ используют альтернативные идиомы, например, в то время как 'c' printf
функция принимает переменные аргументы, c++ cout
объект использует <<
перегрузку, которая затрагивает безопасность типов и ADT (возможно, за счет простоты реализации).
std::initializer_lists
... И это уже вносит огромную сложность в простую задачу.
Помимо varargs или перегрузки, вы можете собирать свои аргументы в 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);
Таким образом, вы получите безопасность типов, и логический смысл этих переменных аргументов будет очевиден.
Конечно, у этого подхода могут быть проблемы с производительностью, но вам не следует беспокоиться о них, если вы не уверены, что не можете заплатить цену. Это своего рода "Pythonic" подход к C ++ ...
Единственный способ - использование аргументов переменных в стиле C, как описано здесь . Обратите внимание, что это не рекомендуемая практика, так как она не безопасна и подвержена ошибкам.
Не существует стандартного C ++ способа сделать это без использования varargs в стиле 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
.
Возможно, вы хотите перегрузку или параметры по умолчанию - определите ту же функцию с параметрами по умолчанию:
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 );
... или вы можете искать соглашения о вызовах v_args из C.
Если вы знаете диапазон количества аргументов, которые будут предоставлены, вы всегда можете использовать перегрузку некоторых функций, например
f(int a)
{int res=a; return res;}
f(int a, int b)
{int res=a+b; return res;}
и так далее...
Использование вариадических шаблонов, пример для воспроизведения, console.log
как видно из JavaScript:
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;
}
};
Как уже говорили другие, С-стиль varargs. Но вы также можете сделать нечто подобное с параметрами по умолчанию.
Теперь это возможно ... используя boost any и шаблоны. В этом случае тип аргументов может быть смешанным
#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);
}
Объедините решения C и C ++ для семантически простого, производительного и наиболее динамичного варианта. Если вы облажались, попробуйте что-нибудь еще.
// 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 аргументами. Под капотом вы можете распаковать его так или иначе.
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;
}
}
Обычная версия