Ничего не выйдет! Требуется хотя бы 1 обязательный параметр — это принципиальный момент.
Почитай вот это
Для определения функции с переменным числом параметров необходимо знать две вещи: как задать переменный список в прототипе функции и как обрабатывать переменный список в теле функции. Переменный список параметров задается в заголовке функции многоточием:
int f(…)
Этот заголовок не вызывает у компилятора протестов. Такая запись означает, что при определении функции компилятору неизвестны ни количество параметров, ни их типы, и он, естественно, не может ничего проверить. Количество параметров и их типы становятся известными только при вызове функции.
Однако у программиста с написанием таких функций сразу возникают проблемы. Ведь имена параметров отсутствуют! Спрашивается, как же написать алгоритм обработки, не зная имен параметров? Естественно, только одним способом – использовать для доступа к параметрам указатель! Вспомним, что все параметры при вызове помещаются в стек. Если мы каким-то образом установим указатель на начало списка параметров в стеке, то манипулируя с указателем, мы, в принципе, можем «достать» все параметры!
Таким образом, совсем пустой список параметров мы написать не можем — должен быть прописан хотя бы один явный параметр, адрес которого мы собираемся получить при выполнении программы. Заголовок такой функции может выглядеть так:
int f(int k...)
Как мы видим, ни запятая, ни пробел после параметра не обязательны, хотя можно их и прописать.
При написании функции с переменным числом параметров помимо алгоритма обработки программист должен разрабатывать и алгоритм доступа к параметрам. Нам надо ответить на два вопроса: как «перебирать» параметры;как закончить перебор.
Ответ на первый вопрос очевиден – надо изменять значение указателя, чтобы переместиться на следующий параметр. Отсюда следует, что указатель должен быть типизированным, поскольку с бестиповым указателем нельзя выполнять арифметические операции. Это же означает, что программист при разработке функции с переменным числом параметров должен отчетливо себе представлять типы аргументов, которые будет обрабатывать функция. Кроме того, способ передачи параметров должен быть одинаковым для всех: либо все – по значению, либо все – по ссылке, либо все – по указателю.
Передача аргумента не того типа, который задумывался, или не тем способом, который подразумевался при разработке, приведет к катастрофическим последствиям – компилятор-то ничего не проверяет.
Ответ на второй вопрос не вызывает затруднений. Это можно сделать одним из двух способов:
* явно передать среди обязательных параметров количество аргументов;
* добавить в конец списка аргумент с уникальным значением, по которому будет определяться конец списка параметров;
И тот, и другой способ имеют право на жизнь — все определяется потребностями задачи и вкусами программиста.
Попробуем написать функцию, вычисляющую среднее арифметическое своих аргументов. Пусть наша функция обрабатывает последовательность чисел типа double. Для ограничения списка используем второй способ: последним значением списка параметров будет ноль. Это означает, что все числа должны быть положительными. Ограничение, конечно, слишком сильное, но мы не будем придавать этому большое значение, так как нам важнее разобраться с доступом к списку параметров.
Таким образом, определение функции будет выглядеть так:
double f(double n, ...) //--заголовок с переменным числом параметров
{ double *p = &n; //--установились на начало списка параметровdouble sum = 0, count = 0;
while (*p) //--пока аргумент не равен нулю
{ sum+=(*p); //--суммируем аргумент
p++; //--«перемещаемся на следующий аргумент
count++; //--считаем количество аргументов
}
return (sum/count); //--вычисляем среднее
}
Вызов такой функции может выглядеть таким образом:
double y = f(1.0, 2.0, 3.0, 4.0, 0.0);
Переменная y получит значение 2.5. Интересно, что попытка вызвать такую функцию с целыми аргументами f(1,2,3,0) в Visual C++ 6 приводит к явно неверному результату, в то время как в системе Borland выдается неверный, но правдоподобный результат 0.5. Интересно, что такой же результат получается в Visual C++ 6, если завершить список целых параметров двумя нулями: f(1,2,3,0,0). Это потому, что типы параметров не проверяются.
Реализация функции, которая в качестве первого параметра получает количество аргументов, на первый взгляд, не вызывает затруднений. Однако, если первый аргумент – целое число, то требуется преобразование указателя. И тут не все варианты проходят. Не будет работать такой вариант:
double f(int n, ...) //--количество элементов
{ int *p = &n; //--указатель – «целый»double sum = 0, count = n;
for (;n--;(double *)p++) //--преобразование указателя в «double»
sum+=(*p);
return (sum/count);
}
Такой вариант тоже неработоспособен
double f(int n, ...) //--количество элементов
{ double *p = (double *)&n; //--преобразование адресаdouble sum = 0, count = n;
for (;n--;p++) //--изменение указателя
sum+=(*p);
return (sum/count);
}
Причина кроется в том, что изменение указателя производится на столько байт, сколько в памяти занимает базовый тип. В обоих случаях мы установились не на начало списка double-параметров, а на sizeof(int) байтов «раньше» — на целую переменную. И от этого адреса происходит изменение указателя на 8 (размер переменных типа double), что приводит к совершенно неверным результатам. Решение заключается в том, чтобы сначала изменить «целый» указатель, а потом уже его преобразовать в double*. Так всегда необходимо делать, если тип первого параметра отличается от типов отсутствующих параметров.
double f(int n, ...) //--количество элементов
{ int *p = &n;
p++; //--изменение «целого» на sizeof(int)double *pp = (double *)p; //--преобразование адресаdouble sum = 0, count = n;
for (;n--;pp++) //--правильное увеличение на 8
sum+=(*pp);
return (sum/count);
}
Преобразование указателя написано в стиле С, но только потому, что так короче.
То же самое — стандартными средствами
В качестве примера рассмотрим реализацию функции вычисления среднего арифметического (вариант с параметром-количеством аргументов) с использованием этих макросов.
Очень похоже выглядит и вариант с нулем в конце списка.
double f(double a, ...)
{ va_list p; //--объявление указателяdouble sum = 0, count = 0;
va_start(p, a); //--инициализация указателяdouble k = a; //--промежуточная переменная do {
sum+=k;
count++;
} while(k=va_arg(p,double)); //--пока не ноль в списке-передвигаемся
va_end(p); //--«закрыли» указательreturn (sum/count);
}
Однако в этом случае удобно использовать цикл do while, так как указатель p сразу устанавливается на слагаемое. Передвижение по списку выполняется прямо в условии цикла, что обеспечивает одновременную проверку на ноль.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Там обсуждались безымянные параметры, а я спросил про VA.
Но всё равно там нет ответа, как получить указатель на первый параметр.
Кстати, там использовалась какая-то штука "__formal" — я попробовал написать именно так — не скомпилилось! (VC6).
— Подумал грешным делом что это резервное слово или макрос, который я ещё не знаю
Что это такое?
LVV>double f(double n, ...) //--заголовок с переменным числом параметров
LVV>{ double *p = &n; //--установились на начало списка параметров
LVV> double sum = 0, count = 0;
LVV> while (*p) //--пока аргумент не равен нулю
LVV> { sum+=(*p); //--суммируем аргументLVV> p++; //--«перемещаемся на следующий аргумент
C>Там обсуждались безымянные параметры, а я спросил про VA.
А почему бы не сделать так:
template<typename T>
void f(T t,...)
Есть параметр и перемнное количество параметров C>Но всё равно там нет ответа, как получить указатель на первый параметр.
Потому что нет стандартного способа.
Можно спомощью ассемблера пройтись по стеку, однако код не будет переносим.
C>Кстати, там использовалась какая-то штука "__formal" — я попробовал написать именно так — не скомпилилось! (VC6).
Может этого нет в VC6. C>- Подумал грешным делом что это резервное слово или макрос, который я ещё не знаю C>Что это такое?
Специльное слово от MS
Здравствуйте, achp, Вы писали:
A>Здравствуйте, LaptevVV, Вы писали:
LVV>>
LVV>>double f(double n, ...) //--заголовок с переменным числом параметров
LVV>>{ double *p = &n; //--установились на начало списка параметров
LVV>> double sum = 0, count = 0;
LVV>> while (*p) //--пока аргумент не равен нулю
LVV>> { sum+=(*p); //--суммируем аргумент
A>LVV> p++; //--«перемещаемся на следующий аргумент
LVV>> count++; //--считаем количество аргументов
LVV>> }
LVV>> return (sum/count); //--вычисляем среднее
LVV>>}
LVV>>
A>И это пишут в книжках? Кошмар!
A>Почему p++? Почему не p--? Почему не p = reinterpret_cast<double*>(reinterpret_cast<char*>(p) + 9)?
Именно р++. Все проверялось на VC6 и BCB6 — правильно работает. А пишут для объяснения реализации стандартных средств va_...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, achp, Вы писали:
LVV>>Именно р++. Все проверялось на VC6 и BCB6 — правильно работает.
A>А если проверить на машине, где стек растет вверх?
В ближайших от меня окрестностях ничего кроме Интела нету. ((
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Имеется ввиду вот это?
Очень верный вопрос. Да, именно так:
//Компилировать нужно в режиме С, а не С++.#include <varargs.h>
#include <stdio.h>
void f(va_alist)
va_dcl
{
va_list marker;
va_start(marker);
printf(va_arg(marker, char*));
va_end(marker);
}
int main()
{
f("Hello, world!\n");
}
На самом деле в большинстве случаев компилятору не нужен последний именованный аргумент функции (разве он его сам не знает ). Более того, часто не нужен вообще ни один именованный аргумент, как лежат аргументы в стеке компилятору известно, откуда они начинаются тоже. Так что реализовать built-in, который все достает без лишний разговоров труда не составляет.
(achp успел написать раньше меня ). Писать "p++" не написав большого жирного предупреждения, что так никогда-никогда делать не нельзя недопустимо, код совершенно не переносим, даже если с ростом стека все в порядке, так как именованные аргументы могут передаваться на регистрах, а остальные в стеке. "Компиляторская магия" может спокойно это скрыть за va_start/va_arg.
C>Не работает в VC6... Не находит макросы va_alist и va_dcl. Когда подключаю <varargs.h>, кричит на то, что va_list, va_start — переопределены.
Там же написано компилировать в режиме С (опция /Tc), если расширениея файла .cpp.
>cl /Tc test152.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
test152.cpp
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
/out:test152.exe
test152.obj
Если не заморачиваться, и не обожествлять MS, и думать о переносимости, есть также другой класс решений:
1) определяем класс T, который будет отрабатывать Variable Argument List.
2) определить в нем функции типа T& func(A a); (это может быть и операторы, скажем запятая, или << как в STL-потоках).
В итоге имеем:
T formatter;
formatter.add("Vasya is ").add(age).add(" years old.");
С операторами может быть красивее.
В любом случае, это решение более безопасное и переносимое, чем прямая работа с _AddressOfReturnAddress и предположениями о типах передаваемых параметров.