Читаем Программирование. Принципы и практика использования C++ Исправленное издание полностью

Вызов f(y) является корректным, если инициализация T x=y; произошла и если обе переменные с именем x могут принимать одно и то же значение. Рассмотрим пример.

void f(double);

void g(int y)

{

  f(y);

  double x(y); // инициализируем переменную x значением

               // переменной y (см. раздел 8.2.2)

}

Обратите внимание на то, что для инициализации переменной x значением переменной y необходимо преобразовать переменную типа int в переменную типа double. То же самое происходит при вызове функции f. Значение типа double, полученное функцией f, совпадает со значением, хранящимся в переменной x.

  Преобразования часто оказываются полезными, но иногда преподносят сюрпризы (см. раздел 3.9.2). Следовательно, работая с преобразованиями, следует проявлять осторожность. Передача переменной типа double в качестве аргумента функции, ожидающей переменную типа int, редко можно оправдать.

void ff(int);

void gg(double x)

{

  ff(x); // как понять, имеет ли это смысл?

}

Если вы действительно хотите усечь значение типа double до значения типа int, то сделайте это явно.

void ggg(double x)

{

  int x1 = x; // усечение x

  int x2 = int(x);

  ff(x1);

  ff(x2);

  ff(x);      // усечение x

  ff(int(x));

}

Таким образом, следующий программист, просматривая этот код, сможет увидеть, что вы действительно думали об этой проблеме. 

<p id="AutBody_Root148"><strong>8.5.8. Реализация вызова функции</strong></span><span></p>

 Как же на самом деле компилятор выполняет вызов функции? Функции expression, term и primary, описанные в главах 6 и 7, прекрасно подходят для иллюстрации этой концепции за исключением одной детали: они не принимают никаких аргументов, поэтому на их примере невозможно объяснить механизм передачи параметров. Однако погодите! Они должны принимать некую входную информацию; если бы это было не так, то они не смогли бы делать ничего полезного. Они принимают неявный аргумент, используя объект ts класса Token_stream для получения входной информации; объект ts является глобальной переменной. Это несколько снижает прозрачность работы программы. Мы можем улучшить эти функции, позволив им принять аргумент типа Token_stream&. Благодаря этому нам не придется переделывать ни один вызов функции.

Во-первых, функция expression совершенно очевидна; она имеет один аргумент (ts) и две локальные переменные (left и t).

double expression(Token_stream& ts)

{

  double left = term(ts);

  Token t = ts.get;

  // ...

}

Во-вторых, функция term очень похожа на функцию expression, за исключением того, что имеет дополнительную локальную переменную (d), которая используется для хранения результата деления (раздел case '/').

double term(Token_stream& ts)

{

  double left = primary(ts);

  Token t = ts.get;

  // ...

  case '/':

  {

    double d = primary(ts);

    // ...

  }

  // ...

}

В-третьих, функция primary очень похожа на функцию term, за исключением того, что у нее нет локальной переменной left.

double primary(Token_stream& ts)

{

  Token t = ts.get;

  switch (t.kind) {

  case '(':

    { double d = expression(ts);

    // ...

  }

    // ...

  }

}

Теперь у этих функций нет скрытых глобальных переменных, и они превосходно подходят для иллюстрации: у них есть аргумент и локальные переменные, и они вызывают друг друга. Возможно, вы захотите освежить память и еще раз посмотреть, как выглядят эти функции в законченном виде, но все их основные свойства, относящиеся к механизму вызова функций, уже перечислены.

  При вызове функции реализация языка программирования создает структуру данных, содержащую копии всех ее параметров и локальных переменных. Например, при первом вызове функции expression компилятор создает структуру, напоминающую показанную на рисунке.

Детали зависят от реализации, но в принципе к ним относится информация о том, что функция должна вернуть управление и некое значение в точку вызова. Такую структуру данных называют записью активации функции (function activation record), или просто активационной записью. Каждая функция имеет свою собственную запись активации. Обратите внимание на то, что с точки зрения реализации параметр представляет собой всего лишь локальную переменную.

Теперь функция expression вызывает term, поэтому компилятор создает активационную запись для вызова функции term.

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже