Бьерн Страуструп - Язык программирования С++. Главы 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);       // ошибка: здесь нужно преобразовывать тип

      }