Параметры-константы
В заголовках некоторых функций можно встретить объявление формального параметра с добавлением служебного слова const. Это означает, что в теле программы значение формального параметра меняться не должно. Но ведь тот программист, который пишеттело функции, вряд ли захочет по собственной воле изменять значение параметра, зная, что компилятор предупредит такую ошибку. В чем же смысл объявления некоторых параметров константными?
Оказывается, в этом скрывается некоторая уловка – если параметр объявлен как константная ссылка, то в качестве фактического аргумента в этом случае можно задавать не только имя переменной, но и выражение. Таким образом, параметр-ссылкауподобляется параметру-значению. Правда, при этом значение фактического аргумента не попадает в стек. Оно вычисляется и помещается в некоторую скрытую переменную в теле вызывающей функции. А вызываемой функции сообщается адрес этой скрытой переменной, что не меняет логику работы со ссылками. Но запись по этому адресу будет блокирована.
Параметры по умолчанию
В языке C++ допускается определение функций, у которых в заголовке указаны значения некоторых параметров:
double mid1(double x=0.5, double y=0.5)
{ return (x+y)/2.; }
К такой функции можно обратиться с одним (первым) аргументом или вообще без аргументов:
z=mid1(0.75); //результат равен 0.625=(0.75+0.5)/2.
p=mid1(); //результат равен 0.5=(0.5+0.5)/2.
Значения параметров по умолчанию могут быть заданы не выборочно, а только для параметров, идущих подряд и расположенных в конце списка. Это означает, что компилятор расценивает как ошибку следующий заголовок функции:
double mid2(double x=0.5,double y)
В отличие от этого следующий заголовок считается правильным:
double mid3(double x,double y=0.5)
Обратите внимание на одну практическую деталь. Функция с параметрами по умолчанию работает правильно в двух случаях. Во-первых, если ее описание находится выше вызывающей функции и в заголовке функции содержится информация о параметрах поумолчанию. Во-вторых, если ее описание находится ниже и в заголовке функции отсутствуют сведения о параметрах по умолчанию, но они содержатся в прототипе. Одновременное упоминание значений по умолчанию и в заголовке функции, и в прототипе приводит к сообщению об ошибке как в системе BC 3.1, так и в ВСВ. Ниже приводится один из вариантов правильного оформления такой программы:
#include <iostream.h>
#include <conio.h>
double mid(double x=1.,double y=1.);
void main()
{ //double mid(double x,double y);
double x=0.4,y=0.2,z;
z=mid(x,y); cout<<"z="<<z<<endl;
z=mid(x); cout<<"z="<<z<<endl;
z=mid(); cout<<"z="<<z<<endl;
getch();
}
double mid(double x,double y)
{ return (x+y)/2.; }
Функции с переменным количеством аргументов
Постоянная работа с функциями типа printf или scanf вызывает у программистов зависть – это же функции с переменным количеством аргументов. А как написать свою функцию, обрабатывающую столько параметров, сколько будет задано в обращении, и, естественно, допускающую задание разного количества аргументов?
Рассмотрим в качестве примера функцию, вычисляющую среднее арифметическое нескольких своих аргументов, имеющих типdouble. Вызванная функция может догадаться о количестве переданных ей параметров только в том случае, если ей сообщают (например, значением первого аргумента) это число n, либо список параметров завершается обусловленным признаком – например, нулевым значением последнего параметра.
Если мы собираемся сообщать функции количество передаваемых ей аргументов, то заголовок функции можно оформить следующим образом:
double mid_var(int n,...)
Три точки в конце списка предупреждают компилятор о том, что он не должен контролировать количество и типы следующих аргументов. Все заботы о доступе к списку параметров переменной длины берет на себя вызываемая функция. Предположим, далее, что все аргументы передаются в функцию mid_var как значения, т.е. к моменту передачи управления функции они находятся в стеке. Добраться до них можно следующим образом. Заведем указатель типа int и занесем в него адрес формального параметра n (система знает, где находится стек, и адрес n ей доступен):
int *ip=&n;
Продвинем указатель ip на 1, т.е. переместимся на адрес начала следующего параметра, и занесем его в новый указатель dp уже типа double:
ip++; //переход на адрес первого слагаемого
double *dp=(double *)ip; //преобразование типа указателя
Теперь адрес начала списка слагаемых у нас есть, количество слагаемых мы тоже знаем, поэтому все остальное – дело техники. Окончательный вид функции таков:
double mid_var(int n,...)
{ int *ip=&n+1;
double *dp=(double *)ip;
double s=0.;
for(int j=0; j<n; j++)
s += dp[j]; //или s += *(dp+j); или s += *(dp++);
return s/n;
}
Теперь попытаемся построить аналогичную функцию, которая суммирует свои аргументы до тех пор, пока не встречает нулевое слагаемое. Она устроена еще проще:
double mid_var(double a1,...)
{ double *dp=&a1;
double s=0;
int c=0;
while(*dp != 0)
{ s += *(dp++); c++; }
return s/c;
}
Аналогичные функции можно построить, когда список передаваемых параметров состоит из переменного количества однотипных указателей. Только здесь придется использовать не просто указатели типа *dp, а "двойные" указатели типа **dp. И доставать значения нужных данных придется также через двойные указатели s += (**dp);
В файле stdarg.h находится несколько функций (точнее, макроопределений) которые обеспечивают перемещение по списку параметров, завершающемуся нулем:
va_list p; //объявление указателя на список параметров
va_start(p,p1); //установка указателя списка на последний явный параметр
va_arg(p,тип); //перемещение указателя на очередной неявный параметр
va_end(p); //уничтожение указателя на список параметров
Продемонстрируем использование этих средств на примере той же самой функции mid_var:
double mid_var(int n,...) //функции передают количество параметров
{ va_list p;
double s=0,c=0;
va_start(p,n);
while(n--) //до тех пор, пока n != 0
{ s += va_arg(p,double); c++; }
va_end(p);
return s/c;
}
Если список параметров начинается с первого слагаемого a1, то программа меняется очень незначительно:
double mid_var(double a1,...)
{ va_list p;
double s=0,c=0,u=a1;
va_start(p,a1);
do {s += u; c++; }
while(u=va_arg(p,double)); //до тех пор, пока u != 0
va_end(p);
return s/c;
}
На наш взгляд, применение обычных указателей выглядит несколько проще, чем использование стандартных средств.