Как было сказано в разделе 4.14, значение типа double может быть преобразовано в int. Следовательно, считать такой вызов ошибочным было бы слишком сурово. Вместо этого аргументы неявно преобразуются в int (отбрасыванием дробной части) и таким образом требования, налагаемые на типы параметров, выполняются. Поскольку при подобном преобразовании возможна потеря точности, хороший компилятор выдаст предупреждение.
Вызов превращается в
gcd( 3, 6 );
что дает в результате 3.
С++ является строго типизированным языком. Компилятор проверяет аргументы на соответствие типов в каждом вызове функции. Если тип фактического аргумента не соответствует типу формального параметра, то производится попытка неявного преобразования. Если же это оказывается невозможным или число аргументов неверно, компилятор выдает сообщение об ошибке. Именно поэтому функция должна быть объявлена до того, как программа впервые обратится к ней: без объявления компилятор не обладает информацией для проверки типов.
Пропуск аргумента при вызове или передача аргумента неуказанного типа часто служили источником ошибок в языке С. Теперь такие погрешности обнаруживаются на этапе компиляции.
Какие из следующих прототипов функций содержат ошибки? Объясните.
(a) set( int *, int );
(b) void func();
(c) string error( int );
(d) arr[10] sum( int *, int );
Напишите прототипы для следующих функций:
Функция с именем compare, имеющая два параметра типа ссылки на класс matrix и возвращающая значение типа bool.
Функция с именем extract без параметров, возвращающая контейнер set для хранения значений типа int. (Контейнерный тип set описывался в разделе 6.13.)
Имеются объявления функций:
double calc( double );
int count( const string , char );
void sum( vectorint , int );
vectorint vec( 10 );
Какие из следующих вызовов содержат ошибки и почему?
(a) calc( 23.4, 55.1 );
(b) count( "abcda", 'a' );
(c) sum( vec, 43.8 );
(d) calc( 66 );
7.3. Передача аргументов
Функции используют память из стека программы. Некоторая область стека отводится функции и остается связанной с ней до окончания ее работы, по завершении которой отведенная ей память освобождается и может быть занята другой функцией. Иногда эту часть стека называют областью активации.
Каждому параметру функции отводится место в данной области, причем его размер определяется типом параметра. При вызове функции память инициализируется значениями фактических аргументов.
Стандартным способом передачи аргументов является копирование их значений, т.е. передача по значению. При этом способе функция не получает доступа к реальным объектам, являющихся ее аргументами. Вместо этого она получает в стеке локальные копии этих объектов. Изменение значений копий никак не отражается на значениях самих объектов. Локальные копии теряются при выходе из функции.
Значения аргументов при передаче по значению не меняются. Следовательно, программист не должен заботиться о сохранении и восстановлении их значений при вызове функции. Без этого механизма любой вызов мог бы привести к нежелательному изменению аргументов, не объявленных константными явно. Передача по значению освобождает человека от лишних забот в наиболее типичной ситуации.
Однако такой способ передачи аргументов может не устраивать нас в следующих случаях:
* передача большого объекта типа класса. Временные и пространственные расходы на размещение и копирование такого объекта могут оказаться неприемлемыми для реальной программы;
иногда значения аргументов должны быть модифицированы внутри функции. Например, swap() должна обменять значения своих аргументов, что невозможно при передаче по значению:
// swap() не меняет значений своих аргументов!
void swap( int vl, int v2 ) {
int tmp = v2;
v2 = vl;
vl = tmp;
}
*
swap() обменивает значения локальных копий своих аргументов. Те же переменные, что были использованы в качестве аргументов при вызове, остаются неизменными. Это можно проиллюстрировать, написав небольшую программу:
#include iostream
void swap( int, int );
int main() {
int i = 10;
int j = 20;
cout "Перед swap():\ti: "
i "\tj: " j endl;
swap( i, j );
cout "После swap():\ti: "
i "\tj: " j endl;
return 0;
}
Результат выполнения программы:
Перед swap(): i: 10 j: 20
После swap(): i: 10 j: 20
Достичь желаемого можно двумя способами. Первый – объявление параметров указателями. Вот как будет выглядеть реализация swap() в этом случае: