С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 56
Текст из файла (страница 56)
Найдите их и объясните.print( arr, 5 );Упражнение 7.7Перепишите функцию putValues( vector<int> ), приведенную в подразделе 7.3.4,так, чтобы она работала с контейнером list<string>. Печатайте по одному значению настроке. Вот пример вывода для списка из двух строк:( 2 )С++ для начинающих<"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. Например:339С++ для начинающихvoid d_copy( double "src, double *dst, int sz ){/* копируем массив "src" в "dst"* для простоты предполагаем, что они одного размера*/// завершение, если хотя бы один из указателей равен 0if ( !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, иначе произойдет ошибка компиляции. Хотякомпилятор не отвечает за правильность результата, он сможет гарантировать егоналичие. Следующая программа не компилируется из-за двух мест, где программазавершается без возврата значения:340С++ для начинающих// определение интерфейса класса 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() возвращает ссылку:341С++ для начинающихMatrix& grow( Matrix* p ) {Matrix *res;// выделим память для объекта Matrix// большого размера// res адресует этот новый объект// скопируем содержимое *p в *resreturn *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-значение. Любая его модификация затрагивает самобъект. Например:342С++ для начинающих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 в vecint main() {// увеличивает vec[0] на 1get_val( vec.0 )++;// ...}Для предотвращения нечаянной модификации возвращенного объекта нужнообъявить тип возврата как const:const int &get_val( ... )Примером ситуации, когда l-значение возвращается намеренно, чтобы позволитьмодифицировать реальный объект, может служить перегруженный оператор взятияиндекса для класса IntArray из раздела 2.3.7.4.1.
Передача данныхглобальные объектычерезпараметрыичерезРазличные функции программы могут общаться между собой с помощью двухмеханизмов. (Под словом “общаться” мы подразумеваем обмен данными.) В одномслучае используются глобальные объекты, в другом – передача параметров и возвратзначений.int glob;int main() {// что угодноГлобальный объект определен вне функции. Например:}Объект glob является глобальным. (В главе 8 рассмотрение глобальных объектов иглобальной области видимости будет продолжено.) Главное достоинство и одновременноодин из наиболее заметных недостатков такого объекта – доступность из любого местапрограммы, поэтому его обычно используют для общения между разными модулями.Обратная сторона медали такова:•функции, использующие глобальные объекты, зависят от этих объектов и ихтипов.
Использовать такую функцию в другом контексте затруднительно;•при модификации такой программы повышается вероятность ошибок. Даже длявнесения локальных изменений необходимо понимание всей программы в целом;С++ для начинающих•если глобальный объект получает неверное значение, ошибку нужно искать повсей программе. Отсутствует локализация;•используя глобальные объекты, труднее писать рекурсивные функции(Рекурсия возникает тогда, когда функция вызывает сама себя.
Мы рассмотримэто в разделе 7.5.);•если используются потоки (threads), то для синхронизации доступа кглобальным объектам требуется писать дополнительный код. Отсутствиесинхронизации – одна из распространенных ошибок при использовании потоков.(Пример использования потоков при программировании на С++ см. в статье“Distributing Object Computing in C++” (Steve Vinoski and Doug Schmidt) в[LIPPMAN96b].)Можно сделать вывод, что для передачи информации между функциямипредпочтительнее пользоваться параметрами и возвращаемыми значениями.Вероятность ошибок при таком подходе возрастает с увеличением списка.
Считается, чтовосемь параметров – это приемлемый максимум. В качестве альтернативы длинномусписку можно использовать в качестве параметра класс, массив или контейнер. Онспособен содержать группу значений.Аналогично программа может возвращать только одно значение. Если же логика требуетнескольких, некоторые параметры объявляются ссылками, чтобы функция могланепосредственно модифицировать значения соответствующих фактических аргументов ииспользовать эти параметры для возврата дополнительных значений, либо некоторыйкласс или контейнер, содержащий группу значений, объявляется типом, возвращаемымфункцией.Упражнение 7.9Каковы две формы инструкции return? Объясните, в каких случаях следуетиспользовать первую, а в каких вторую форму.Упражнение 7.10vector<string> &readText( ) {vector<string> text;string word;while ( cin >> word ) {text.push_back( word );// ...}// ....return text;Найдите в данной функции потенциальную ошибку времени выполнения:}Упражнение 7.11Каким способом вы вернули бы из функции несколько значений? Опишите достоинства инедостатки вашего подхода.344С++ для начинающих3457.5.
РекурсияФункция, которая прямо или косвенно вызывает сама себя, называется рекурсивной.int rgcd( int vl, int v2 ){if ( v2 != 0 )return rgcd( v2, vl%v2 );return vl;Например:}Такая функция обязательно должна определять условие окончания, в противном случаерекурсия будет продолжаться бесконечно. Подобную ошибку так иногда и называют –бесконечная рекурсия. Для rgcd() условием окончания является равенство нулю остатка.Вызовrgcd( 15, 123 );возвращает 3 (см. табл. 7.1).Таблица 7.1.