Г. Шилтд - Самоучитель C++ (PDF) (1114887), страница 23
Текст из файла (страница 23)
(Другими словами, указатели х.р и num.p теперь не ссылаются наодну и ту же область памяти.) Если бы не был создан конструктор копий, топоразрядная инициализация при выполнении инструкции array x= пшп привела бы к тому, что массивы х и пшп оказались бы в одной и той же области памяти! (То есть, указатели х.р и num.p ссылались бы на одну и ту же областьпамяти.)Глава 5. Перегрузка функций153Конструктор копий вызывается только для инициализации. Например, следующая последовательность инструкций не ведет к вызову определенного впредыдущей программе конструктора копий:array a (10};array b(10) ;b = а; // конструктор копий не вызываетсяВ данном случае инструкция b = а представляет собой операцию присваивания.2. Чтобы понять, как конструктор копий помогает предотвратить некоторыепроблемы, связанные с передачей функциям объектов определенных типов,рассмотрим следующую, неправильную программу:/ / В этой программе имеется ошибка#include <iostream>^include <cstring>^include <cstdlib>using namespace std;class strtype (char *p;public:strtype(char *s);-strtype() {delete [] p;}char *get() (return p;}strtype:: strtypefchar *s)int 1;l=strlen(s)+1;p=new char[1];cout « "Ошибка выделения памяти\п";exit(1} ;strcpy(p, s);void show(strtype x)char *s;s=x.get() ;cout « s « "\n";1_154СамоучительC++int main (){strtype a ("Hello"), b("There");show (a) ;show(b) ;return 0;В этой программе, когда объект типа strtype передается в функцию show(),создается поразрядная копия объекта (поскольку не был определен конструктор копий) и передается параметру х.
Таким образом, когда функция возвращает свое значение, х выходит из области видимости и удаляется. Это,естественно, приводит к вызову деструктора объекта х, который освобождаетобласть памяти по адресу х.р. Однако освобожденная память — это та самаяпамять, которую продолжает занимать объект, используемый при вызовефункции. Это приводит к ошибке.Решение предыдущей проблемы лежит в определении конструктора копийдля класса strtype, который при создании копии объекта типа strtype выделяет для нее память. Такой подход используется в следующей, исправленнойверсии программы:/* В этой программе используется конструктор копирования, чтопозволяет передавать функции объекты типа strtype*/# include <iostream># include <cstring># include <cstdlib>using namespace std;class strtype {char *p;public:strtype(char *s) ;// конструкторstrtype (const strtype &o) ;// конструктор копий~strtype(} {delete [] p; }// деструкторchar *get() {return p; }};// Обычный конструкторstrtype: : strtype (char *s)(int 1;l=strlen(s) +1;p=new char [1] ;if(!p) (cout « "Ошибка выделения памяти\п";Глава5.Перегрузкафункций_155exit (1) ;}strcpyfp, з) ;}// Конструктор копийstrtype: istrtype (const strtype &o)tint 1;l=strlen(o.p) +1;p=new charfl] ; // выделение памяти для новой копииif(!p) {cout « "Ошибка вьщеления памяти\п";exit(l) ;}strcpy (р, о.р} ; // копирование строки в копию}void show (strtype x){char *s;s=x.get () ;cout « s « "\n";}int mainO{strtype a ("Hello"], b( "There");show (a) ;show(b) ;return 0;Теперь, когда функция show() завершается и объект х выходит из области видимости, память, на которую ссылается указатель х.р (освобождаемая память), — это уже не та память, которая используется переданным в функциюобъектом.[Упражненияения]1.
Конструктор копий вызывается и в тех случаях, когда функция генерируетвременный объект, используемый в качестве ее возвращаемого значения (длятех функций, которые возвращают объекты). Зная это, рассмотрим следующий результат работы программы:156_СамоучительC++Работа обычного конструктораРабота обычного конструктораРабота конструктора копийЭти строки появились в результате работы следующей программы. Объясните, что именно там происходит и почему.^include <iostream>using namespace std;class myclass {public:myclass () ;myclass (const myclass &o) ;myclass f ( ) ;// Обычный конструкторmyclass : :myclass ( ){cout « "Работа обычного конструктора\п";}/ / Конструктор копийmyclass: imyclass (const myclass So){cout « "Работа конструктора копийХп";i// Возвращение объектаmyclass myclass: :f(){myclass temp;return temp;int main!){myclass obj;obj=obj.f{);return 0;2.
Объясните, что в следующей программе неправильно, и исправьте ее.// В этой программе имеется ошибка#include <iostream>Глава5.Перегрузкафункций_157^include <cstdlib>using namespace std;class rayclass {int *p;public:myclass (int i) ;~myclass() (delete p; }friend int getval (myclass o) ;myclass: :myclass(int i){p=new int;if(!p) {cout « "Ошибка выделения памяти\п";exit(l) ;int getval(myclass o)return *o.p; / / получение значенияintmain()myclass a ( l } , b ( 2 ) ;cout « getval(a) « " " « getval(b);cout « "\n";cout « getval(a) « " " « getval(b);return 0;J3. Объясните своими словами, зачем нужен конструктор копий и чем он отличается от обычного конструктора.5.3. Устаревшее ключевое слово overloadВ ранних версиях C++ для создания перегружаемых функций требовалосьключевое слово overload. Хотя ключевое слово overload в современных компиляторах больше не поддерживается, вы все еще можете встретить его в существующих профаммах, поэтому полезно знать, как оно использовалось.158__Самоучитель C++Ниже показана основная форма ключевого слова overload:overload имя функции;Здесь имя_функции — это имя перегружаемой функции.
Перед этой инструкцией должно находиться объявление перегружаемой функции. Например,следующая инструкция сообщает компилятору, что вы будете перегружатьфункцию timer():overload timer ();Ключевое слово overload является устаревшим и в современных компиляторах C++ не поддерживается.5.4. Аргументы по умолчаниюВ синтаксисе C++ имеется элемент, который имеет непосредственное отношение к перегрузке функций и называется аргументом по умолчанию(default argument). Аргумент по умолчанию позволяет вам, если при вызовефункции соответствующий аргумент не задан, присвоить параметру значение по умолчанию. Как вы увидите далее, применение аргумента по умолчанию является скрытой формой перегрузки функций.Чтобы передать параметру аргумент по умолчанию, нужно в инструкцииопределения функции приравнять параметр тому значению, которое вы хотите передать, когда при вызове функции соответствующий аргумент не будет указан.
Например, в представленной ниже функции двум параметрам поумолчанию присваивается значение 0:void f l i n t а = О, int Ь = 0 ) ;Обратите внимание, что данный синтаксис напоминает инициализацию переменных. Теперь эту функцию можно вызвать тремя различными способами. Во-первых, она может вызываться с двумя заданными аргументами. Вовторых, она может вызываться только с первым заданным аргументом.В этом случае параметр b по умолчанию станет равным нулю. Наконец,функция f() может вызываться вообще без аргументов, при этом параметрыа и b по умолчанию станут равными нулю.
Таким образом, все следующиевызовы функции f() правильны:f(); // а и b по умолчанию равны Оf(10); // а равно 10, b по умолчанию равно Оf(10, 99); // а равно 10, b равно 99Глава 5. Перегрузка функцийJ59Из этого примера должно быть ясно, что невозможно передать по умолчанию значение а и при этом задать Ь.Когда вы создаете функцию, имеющую один или более передаваемых поумолчанию аргументов, эти аргументы должны задаваться только один раз:либо в прототипе функции, либо в ее определении, если определение предшествует первому использованию функции.
Аргументы по умолчанию нельзя задавать одновременно в определении и в прототипе функции. Этоправило остается в силе, даже если вы просто дублируете одни и те же аргументы по умолчанию.Как вы, вероятно, догадываетесь, все параметры, задаваемые по умолчанию,должны указываться правее параметров, передаваемых обычным путем.Больше того, после того как вы начали определять параметры по умолчанию, параметры, которые по умолчанию не передаются, уже определятьнельзя.Еще несколько слов об аргументах по умолчанию: они должны быть константами или глобальными переменными. Они не могут быть локальнымипеременными или другими параметрами.1.
Программа для иллюстрации вышеописанной функции:// Первый простой пример аргументов по умолчанию#include <iostream>using namespace std;void f ( i n t a = 0, int b = 0){cout « "a: " « a « ", b: " « b;cout « ' \ n r ;int main ()f<10);f(10, 9 9 ) ;return 0;}Как и следовало ожидать, на экран выводится следующее:а: О, Ь: Оа: 10, Ь: Оа: 10, Ь: 99160___СамоучительC++Запомните, если первый аргумент задан по умолчанию, все последующиепараметры должны также задаваться по умолчанию. Например, такое небольшое изменение функции f() приведет к ошибке при компиляции программы:void f(int a - 0, int b) // Неправильно! Параметр b тоже должен// задаваться по умолчанию{cout « "а: " « а « ", Ь: " « Ь;cout « ' \п' ;2.
Чтобы понять, какое отношение аргументы по умолчанию имеют к перегрузке функций, рассмотрим следующую программу, в которой перегружаетсяфункция rect_area(). Эта функция возвращает площадь прямоугольника./* Расчет площади прямоугольника с использованием перегрузки функций*/#include <iostream>us ing name space s td ;// Возвращает площадь неравностороннего прямоугольникаdouble rect_area (double length, double width){return length * width;// Возвращает площадь квадратаdouble rect_arsa (double 'length){return length * length;)int{main()cout « "площадь прямоугольника 10 x 5.8 равна: ";cout « rect_area(10.0, 5.8) « '\n';cout « "площадь квадрата 10 x 10 равна : " ;cout « rect_area(10.0) « '\n';return 0;В этой программе функция rect_area() перегружается двумя способами. В первом — функции передаются оба размера фигуры.