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

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

339

<

"first string" "second string"

>

Напишите функцию main(), вызывающую новый вариант putValues() со следующим

"put function declarations in header files"

"use abstract container types instead of built-in arrays" "declare class parameters as references"

"use reference to const types for invariant parameters"

списком строк:

"use less than eight parameters"

Упражнение 7.8

В каком случае вы применили бы параметр-указатель? А в каком параметр-ссылку? Опишите достоинства и недостатки каждого способа.

7.4. Возврат значения

В теле функции может встретиться инструкция return. Она завершает выполнение функции. После этого управление возвращается той функции, из которой была вызвана

return;

данная. Инструкция return может употребляться в двух формах: return expression;

Первая форма используется в функциях, для которых типом возвращаемого значения является void. Использовать return в таких случаях обязательно, если нужно принудительно завершить работу. (Такое применение return напоминает инструкцию break, представленную в разделе 5.8.) После конечной инструкции функции подразумевается наличие return. Например:

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

340

 

 

void d_copy( double "src, double *dst, int sz )

 

 

 

 

 

 

{

 

 

 

/* копируем массив "src" в "dst"

 

 

 

* для простоты предполагаем, что они одного размера

 

 

 

*/

 

 

 

// завершение, если хотя бы один из указателей равен 0

 

 

 

if ( !src || !dst )

 

 

 

return;

 

 

 

// завершение,

 

 

 

// если указатели адресуют один и тот же массив

 

 

 

if ( src == dst )

 

 

 

return;

 

 

 

// копировать нечего

 

 

 

if ( sz == 0 )

 

 

 

return;

 

 

 

// все еще не закончили?

 

 

 

// тогда самое время что-то сделать

 

 

 

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

 

 

 

dst[ix] = src[ix];

 

 

 

// явного завершения не требуется

 

 

 

}

 

 

 

 

 

 

 

Во второй форме инструкции return указывается то значение, которое функция должна

 

вернуть. Это значение может быть сколь угодно сложным выражением, даже содержать

 

вызов функции. В реализации функции factorial(), которую мы рассмотрим в

 

следующем разделе, используется return следующего вида:

 

 

 

return val * factorial(val-1);

 

 

 

 

 

В функции, не объявленная с void в качестве типа возвращаемого значения, обязательно

 

использовать вторую форму return, иначе произойдет ошибка компиляции. Хотя

 

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

 

наличие. Следующая программа не компилируется из-за двух мест, где программа

 

завершается без возврата значения:

 

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

341

// определение интерфейса класса Matrix #include "Matrix.h"

bool is_equa1( const Matrix &ml, const Matrix &m2 )

{

/* Если содержимое двух объектов Matrix одинаково,

*возвращаем true;

*в противном случае - false

*/

// сравним количество столбцов

if ( ml.colSize() != m2.co1Size() )

// ошибка: нет возвращаемого значения return;

// сравним количество строк

if ( ml.rowSize() != m2.rowSize() )

// ошибка: нет возвращаемого значения return;

//пробежимся по обеим матрицам, пока

//не найдем неравные элементы

for ( int row = 0; row < ml.rowSize(); ++row ) for ( int col = 0; co1 < ml.colSize(); ++co1 )

if ( ml[row][col] != m2[row][col] ) return false;

//ошибка: нет возвращаемого значения

//для случая равенства

}

Если тип возвращаемого значения не точно соответствует указанному в объявлении функции, то применяется неявное преобразование типов. Если же стандартное приведение невозможно, происходит ошибка компиляции. (Преобразования типов рассматривались в разделе 4.1.4.)

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

Matrix grow( Matrix* p ) { Matrix val;

// ...

return val;

return. Например:

}

grow() возвращает вызывающей функции копию значения, хранящегося в переменной val.

Такое поведение можно изменить, если объявить, что возвращается указатель или ссылка. При возврате ссылки вызывающая функция получает l-значение для val и потому может модифицировать val или взять ее адрес. Вот как можно объявить, что grow() возвращает ссылку:

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

342

Matrix& grow( Matrix* p ) {

Matrix *res;

//выделим память для объекта Matrix

//большого размера

//res адресует этот новый объект

//скопируем содержимое *p в *res return *res;

}

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

Объявляя функцию как возвращающую ссылку, программист должен помнить о двух возможных ошибках:

∙ возврат ссылки на локальный объект, время жизни которого ограничено временем выполнения функции. (О времени жизни локальных объектов речь пойдет в разделе 8.3.) По завершении функции такой ссылке соответствует область

// ошибка: возврат ссылки на локальный объект

Matrix& add( Matrix &m1, Matrix &m2 )

{

Matrix result:

if ( m1.isZero() ) return m2;

if ( m2.isZero() ) return m1;

//сложим содержимое двух матриц

//ошибка: ссылка на сомнительную область памяти

//после возврата

return result;

памяти, содержащая неопределенное значение. Например:

}

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

Matrix add( ... )

∙ функция возвращает l-значение. Любая его модификация затрагивает сам объект. Например:

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

343

#include <vector>

int &get_val( vector<int> &vi, int ix ) { return vi [ix];

}

int ai[4] = { 0, 1, 2, 3 };

vector<int> vec( ai, ai+4 ); // копируем 4 элемента ai в vec

int main() {

//увеличивает vec[0] на 1 get_val( vec.0 )++;

//...

}

Для предотвращения нечаянной модификации возвращенного объекта нужно объявить тип возврата как const:

const int &get_val( ... )

Примером ситуации, когда l-значение возвращается намеренно, чтобы позволить модифицировать реальный объект, может служить перегруженный оператор взятия индекса для класса IntArray из раздела 2.3.

7.4.1. Передача данных через параметры и через глобальные объекты

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

int glob; int main() {

// что угодно

Глобальный объект определен вне функции. Например:

}

Объект glob является глобальным. (В главе 8 рассмотрение глобальных объектов и глобальной области видимости будет продолжено.) Главное достоинство и одновременно один из наиболее заметных недостатков такого объекта доступность из любого места программы, поэтому его обычно используют для общения между разными модулями. Обратная сторона медали такова:

функции, использующие глобальные объекты, зависят от этих объектов и их типов. Использовать такую функцию в другом контексте затруднительно;

при модификации такой программы повышается вероятность ошибок. Даже для внесения локальных изменений необходимо понимание всей программы в целом;

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

344

если глобальный объект получает неверное значение, ошибку нужно искать по всей программе. Отсутствует локализация;

используя глобальные объекты, труднее писать рекурсивные функции (Рекурсия возникает тогда, когда функция вызывает сама себя. Мы рассмотрим это в разделе 7.5.);

если используются потоки (threads), то для синхронизации доступа к глобальным объектам требуется писать дополнительный код. Отсутствие синхронизации одна из распространенных ошибок при использовании потоков. (Пример использования потоков при программировании на С++ см. в статье

“Distributing Object Computing in C++” (Steve Vinoski and Doug Schmidt) в [LIPPMAN96b].)

Можно сделать вывод, что для передачи информации между функциями предпочтительнее пользоваться параметрами и возвращаемыми значениями.

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

Аналогично программа может возвращать только одно значение. Если же логика требует нескольких, некоторые параметры объявляются ссылками, чтобы функция могла

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

Упражнение 7.9

Каковы две формы инструкции return? Объясните, в каких случаях следует использовать первую, а в каких вторую форму.

Упражнение 7.10

vector<string> &readText( ) { vector<string> text;

string word;

while ( cin >> word ) { text.push_back( word ); // ...

}

// ....

return text;

Найдите в данной функции потенциальную ошибку времени выполнения:

}

Упражнение 7.11

Каким способом вы вернули бы из функции несколько значений? Опишите достоинства и недостатки вашего подхода.