Як я можу написати функцію, яка приймає змінну кількість аргументів? Чи можливо це, як?
Як я можу написати функцію, яка приймає змінну кількість аргументів? Чи можливо це, як?
Відповіді:
Напевно, ви не повинні, і ви, ймовірно, можете робити те, що хочете зробити, більш безпечним і простим способом. Технічно для використання змінної кількості аргументів в 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 функцій у розділі Альтернативи зазначено:
- Варіантні шаблони можуть також використовуватися для створення функцій, що приймають змінну кількість аргументів. Вони часто є кращим вибором, оскільки вони не накладають обмежень на типи аргументів, не виконують інтегральних та рекламних операцій із плаваючою комою та є безпечними для типу. (оскільки C ++ 11)
- Якщо всі аргументи змінної мають спільний тип, std :: inicijalizer_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 :: inicijalizer_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 ;
}
Використання різноманітних функцій також має обмеження в аргументах, які ви можете передати, що детально описано в проекті стандарту C ++ у розділі Пункт 7 5.2.2
виклику функцій :
Коли для даного аргументу немає параметра, аргумент передається таким чином, що функція прийому може отримати значення аргументу шляхом виклику 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, можна визначити шаблон-функцію, яка на сайті виклику може викликатись так, ніби це була варидична функція, але з перевагами для :
Ось приклад змішаних типів аргументів
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 ++ використовують альтернативну ідіому, наприклад, тоді як '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);
Таким чином, ви отримаєте безпеку типу, і логічне значення цих різних аргументів буде очевидним.
Безумовно, такий підхід може мати проблеми з роботою, але ви не повинні турбуватися про них, якщо ви не впевнені, що не можете сплатити ціну. Це свого роду підхід "піфонічний" до 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;
}
};
Як говорили інші, вараги у стилі С. Але ви також можете зробити щось подібне з аргументами за замовчуванням.
Можна зараз ... за допомогою 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;
}
}
Простий варіант