Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
85
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

321

(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() обменивает значения локальных копий своих аргументов. Те же переменные, что были использованы в качестве аргументов при вызове, остаются неизменными. Это можно проиллюстрировать, написав небольшую программу:

С++ для начинающих

322

#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

Достичь желаемого можно двумя способами. Первый объявление параметров

//pswap() обменивает значения объектов,

//адресуемых указателями vl и v2

void pswap( int *vl, int *v2 ) { int tmp = *v2;

*v2 = *vl; *vl = tmp;

указателями. Вот как будет выглядеть реализация swap() в этом случае:

}

Функция main() тоже нуждается в модификации. Вместо передачи самих объектов необходимо передавать их адреса:

pswap( &i, &j );

Теперь программа работает правильно:

Перед

swap():

i:

10

j:

20

После

swap():

i:

20

j:

10

Альтернативой может стать объявление параметров ссылками. В данном случае реализация swap() выглядит так:

С++ для начинающих

323

//rswap() обменивает значения объектов,

//на которые ссылаются vl и v2

void rswap( int &vl, int &v2 ) { int tmp = v2;

v2 = vl; vl = tmp;

}

Вызов этой функции из main() аналогичен вызову первоначальной функции swap():

rswap( i, j );

Выполнив программу main(), мы снова получим верный результат.

7.3.1. Параметры-ссылки

Использование ссылок в качестве параметров модифицирует стандартный механизм передачи по значению. При такой передаче функция манипулирует локальными копиями аргументов. Используя параметры-ссылки, она получает l-значения своих аргументов и может изменять их.

В каких случаях применение параметров-ссылок оправданно? Во-первых, тогда, когда без использования ссылок пришлось бы менять типы параметров на указатели (см. приведенную выше функцию swap()). Во-вторых, при необходимости вернуть из функции несколько значений. В-третьих, для передачи большого объекта типа класса. Рассмотрим два последних случая подробнее.

Как пример функции, использующей параметр-ссылку для возврата дополнительного значения, возьмем look_up(), которая будет искать заданную величину в векторе целых чисел. В случае успеха look_up() вернет итератор, указывающий на найденный элемент, иначе на элемент, расположенный за конечным. Если величина содержится в векторе несколько раз, итератор будет указывать на первое вхождение. Кроме того, дополнительный параметр-ссылка occurs возвращает количество найденных элементов.

С++ для начинающих

324

#include <vector>

//параметр-ссылка 'occurs'

//содержит второе возвращаемое значение

vector<int>::const_iterator look_up( const vector<int> &vec,

int

value,

//

искомое значение

int

&occurs )

//

количество вхождений

{

//res_iter инициализируется значением

//следующего за конечным элемента

vector<int>::const_iterator res_iter = vec.end(); occurs = 0;

for ( vector<int>::const_iterator iter = vec.begin(); iter != vec.end();

++iter )

if ( *iter == value )

{

if ( res_iter == vec.end() ) res_iter = iter;

++occurs;

}

return res_iter;

}

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

class Huge { public: double stuff[1000]; }; extern int calc( const Huge & );

int main() {

Huge table[ 1000 ];

// ... инициализация table

int sum = 0;

for ( int ix=0; ix < 1000; ++ix )

//calc() ссылается на элемент массива

//типа Huge

sum += calc( tab1e[ix] );

// ...

копии. Например:

}

Может возникнуть желание использовать параметр-ссылку, чтобы избежать создания копии большого объекта, но в то же время не дать вызываемой функции возможности изменять значение аргумента. Если параметр-ссылка не должен модифицироваться внутри функции, то стоит объявить его как ссылку на константу. В такой ситуации

С++ для начинающих

325

компилятор способен распознать и пресечь попытку непреднамеренного изменения значения аргумента.

В следующем примере нарушается константность параметра xx функции foo(). Поскольку параметр функции foo_bar() не является ссылкой на константу, то нет гарантии, что вызов foo_bar() не изменит значения аргумента. Компилятор

class X;

extern int foo_bar( X& );

int foo( const X& xx ) {

//ошибка: константа передается

//функции с параметром неконстантного типа return foo_bar( xx );

сигнализирует об ошибке:

}

Для того чтобы программа компилировалась, мы должны изменить тип параметра

extern int foo_bar( const X& );

foo_bar(). Подойдет любой из следующих двух вариантов: extern int foo_bar( X ); // передача по значению

int foo( const X &xx ) { // ...

X x2 = xx; // создать копию значения

//foo_bar() может поменять x2,

//xx останется нетронутым

return foo_bar( x2 ); // правильно

Вместо этого можно передать копию xx, которую позволено менять:

}

Параметр-ссылка может именовать любой встроенный тип данных. В частности, разрешается объявить параметр как ссылку на указатель, если программист хочет изменить значение самого указателя, а не объекта, который он адресует. Вот пример

void ptrswap( int *&vl, int *&v2 ) { int *trnp = v2;

v2 = vl; vl = tmp;

функции, обменивающей друг с другом значения двух указателей:

}

Объявление

int *&v1;