Бьерн Страуструп - Язык программирования С++. Главы 2-4 - Передача параметров
ОГЛАВЛЕНИЕ
4.6.3 Передача параметров
При вызове функции выделяется память для ее формальных параметров, и каждый формальный параметр инициализируется значением соответствующего фактического параметра. Семантика передачи параметров тождественна семантике инициализации. В частности, сверяются типы формального и соответствующего ему фактического параметра, и выполняются все стандартные и пользовательские преобразования типа.
Существуют специальные правила передачи массивов ($$4.6.5). Есть возможность передать параметр, минуя контроль типа ($$4.6.8), и возможность задать стандартное значение параметра ($$4.6.7). Рассмотрим функцию:
void f(int val, int& ref)
{
val++;
ref++;
}
При вызове f() в выражении val++ увеличивается локальная копия первого фактического параметра, тогда как в ref++ - сам второй фактический параметр увеличивается сам. Поэтому в функции
void g()
{
int i = 1;
int j = 1;
f(i,j);
}
увеличится значение j, но не i. Первый параметр i передается по значению, а второй параметр j передается по ссылке. В $$2.3.10 мы говорили, что функции, которые изменяют свой передаваемый по ссылке параметр, труднее понять, и что поэтому лучше их избегать (см. также $$10.2.2). Но большие объекты, очевидно, гораздо эффективнее передавать по ссылке, чем по значению. Правда можно описать параметр со спецификацией const, чтобы гарантировать, что передача по ссылке используется только для эффективности, и вызываемая функция не может изменить значение объекта:
void f(const large& arg)
{
// значение "arg" нельзя изменить без явных
// операций преобразования типа
}
Если в описании параметра ссылки const не указано, то это рассматривается как намерение изменять передаваемый объект:
void g(large& arg); // считается, что в g() arg будет меняться
Отсюда мораль: используйте const всюду, где возможно.
Точно так же, описание параметра, являющегося указателем, со спецификацией const говорит о том, что указуемый объект не будет изменяться в вызываемой функции. Например:
extern int strlen(const char*); // из <string.h>
extern char* strcpy(char* to, const char* from);
extern int strcmp(const char*, const char*);
Значение такого приема растет вместе с ростом программы.
Отметим, что семантика передачи параметров отличается от семантики присваивания. Это различие существенно для параметров, являющихся const или ссылкой, а также для параметров с типом, определенным пользователем ($1.4.2).
Литерал, константу и параметр, требующий преобразования, можно передавать как параметр типа const&, но без спецификации const передавать нельзя. Допуская преобразования для параметра типа const T&, мы гарантируем, что он может принимать значения из того же множества, что и параметр типа T, значение которого передается при необходимости с помощью временной переменной.
float fsqrt(const float&); // функция sqrt в стиле Фортрана
void g(double d)
{
float r;
r = fsqrt(2.0f); // передача ссылки на временную
// переменную, содержащую 2.0f
r = fsqrt(r); // передача ссылки на r
r = fsqrt(d); // передача ссылки на временную
// переменную, содержащую float(d)
}
Запрет на преобразования типа для параметров-ссылок без спецификации const введен для того, чтобы избежать нелепых ошибок, связанных с использованием при передаче параметров временных переменных:
void update(float& i);
void g(double d)
{
float r;
update(2.0f); // ошибка: параметр-константа
update(r); // нормально: передается ссылка на r
update(d); // ошибка: здесь нужно преобразовывать тип
}