Г. Шилтд - Самоучитель C++ (PDF) (1114887), страница 29
Текст из файла (страница 29)
Если это то, что вам нужно, нет смысла создавать собственную функцию operator=(). Однако бываютслучаи, когда точное поразрядное копирование нежелательно. В главе 3 привыделении памяти объекту вам было представлено несколько примеров подобного рода. В таких случаях требуется особая операция присваивания.I Примеры ]1. Здесь приведена новая версия класса strtype, различные формы которогоизучались в предыдущих главах. В этой версии оператор = перегружаетсятак, что указатель р при присваивании не перезаписывается.Глава 6. Введение в перегрузку операторов195^include <iostream>#include <cstring>#include <cstdlib>using namespace std;class strtype {char *p;int len;public:strtype (char *s) ;-strtype () {cout « "Освобождение памяти по адресу " « (unsigned) p « ' \ n ' ;delete []p;)char *get() { return p; }strtype &operator= (strtype &ob) ;};strtype: : strtype (char *s){int 1;1 = strlen(s) + 1;p = new char [1] ;if(!p) {cout « "Ошибка выделения памяти\п";exit(l) ;}len = 1;strcpy (p, s) ;}/ / Присваивание.
объектаstrtype fistrtype: :operator= (strtype &ob){// выяснение необходимости дополнительной памятиif {len < ob.len) { // требуется выделение дополнительной памятиdelete!] p;p = new char [ob.len];if(!p) {cout « "Ошибка выделения памяти\п";exit ( 1 ) ;len = ob.len;strcpy (p, ob.p);return *this;/96____СамоучительC++int ma in ()(strtype а ("Привет") , b ("Здесь") ;cout « a . g e t O « ' \ n ' ;cout « b . g e t O « ' \ n ' ;a = b; // теперь указатель р не перезаписываетсяcout « a.getO « ' \ n ' ;cout « b.getO « ' \ n ' ;return 0;Как видите, перегрузка оператора присваивания предотвращает перезаписьуказателя р. При первом контроле выясняется, достаточно ли в объекте слеваот оператора присваивания выделено памяти для хранения присваиваемойему строки.
Если это не так, то память освобождается и выделяется новыйфрагмент. Затем строка копируется в эту память, а длина строки копируетсяв переменную 1еп.Отметьте два важных свойства функции operator=(). Во-первых, в ней используется пара метр- ссылка. Это необходимо для предотвращения созданиякопии объекта, стоящего справа от оператора присваивания.
Как известно попредыдущим главам, при передаче в функцию объекта создается его копия, иэта копия удаляется при завершении работы функции. В этом случае дляудаления копии должен вызываться деструктор, который освобождает память, обозначенную указателем р. Однако память по адресу р все еще необходима объекту, который является аргументом. Параметр -ссылка помогаетрешить проблему.Вторым важным свойством функции operator=() является то, что она возвращает не объект, а ссылку на него. Смысл этого тот же, что и при обычном использовании параметра-ссылки. Функция возвращает временный объект,который удаляется после полного завершения ее работы.
Это означает, что длявременного объекта будет вызван деструктор, который вызовет освобождениепамяти по адресу р, но указатель р (и память на которую он ссылается) все ещенеобходимы для присваивания значения объекту. Поэтому, чтобы избежатьсоздания временного объекта, в качестве возвращаемого значения используетсяссылка.Как вы узнали из главы 5, создание конструктора копий — это другой путьрешения проблем, описанных в двух предыдущих разделах. Но конструкторкопий может оказаться не столь эффективным решением, как ссылка в качестве параметра и ссылка в качестве возвращаемого значения функции.
Этопроисходит потому, что использование ссылки исключает затраты ресурсов,Глава 6. Введение в перегрузку операторов197связанные с копированием объекта в каждом из двух указанных случаев. Каквидите, в C++ часто имеется несколько способов достижения одной и той жецели. Понимание их преимуществ и недостатков ~ это часть процесса вашего становления как профессионального программиста C++.Пусть дано следующее объявление класса, добавьте все необходимое для создания типа динамический массив. То есть выделите память для массива и сохраните указатель на эту память по адресу р. Размер массива в байтахсохраните в переменной size.
Создайте функцию put(), возвращающую ссылку на заданный элемент массива и функцию get(), возвращающую значениезаданного элемента. Обеспечьте контроль границ массива. Кроме этого перегрузите оператор присваивания так, чтобы выделенная каждому массивутакого типа память не была случайно повреждена при присваивании одногомассива другому. (В следующем разделе будет показан более совершенныйспособ решения этого упражнения.)class dynarray {int *p;int size;public:dynarray(int s); // передача размера массива в переменной sint Sput(int i); // возвращение ссылки на элемент iint get(int i}; // возвращение значения переменной i// создайте функцию operator={)6.7.
Перегрузка оператораиндекса массива []Последним оператором, который мы научимся перегружать, будет операториндекса массива []. В C++ при перегрузке оператор [] рассматривается какбинарный. Оператор [] можно перегружать только как функцию-член. Нижепредставлена основная форма оператор -функции — члена класса operator!] ():тип иыя__класса: : opera tor [] (int индекс)II...С технической точки зрения тип параметра не обязательно должен быть целым, но поскольку оператор-функция operator[](), как правило, используется для получения индекса массива, то ее параметр обычно имеет тип int.198Самоучитель C++Чтобы понять, как работает оператор [], представим, что объект О индексируется следующим образом:0[9]Этот индекс транслируется в вызов функции operator[]():О.operator[](9)Таким образом, значение выражения внутри оператора индексирования явно передается функции operator[]() в качестве параметра. При этом указатель this будет ссылаться на объект О, являющийся источником вызова.I Примеры^i/"1.
В следующей программе объявляется состоящий из пяти целых массивarraytype. Каждый элемент массива инициализируется конструктором. Перегруженная функция operator[]() возвращает элемент, заданный ее параметром.# include <iostream>using namespace std;const int SIZE = 5;class arraytype {int a [SIZE] ;public:arraytype ( ) (int i ;for (i=o; i<SIZE; i-H-) a[i] = i;}int operator [] {int i) { return a[i]; }int main ( ){arraytype ob;int i;for{i=0; i<SIZE; icout « ob[i] «return 0;В результате работы программы на экран выводится следующее:0 1 2 3 4Глава6.Введениевперегрузкуоператоров_199В этом и остальных примерах инициализация массива а с помощью конструктора выполнена исключительно в иллюстративных целях и на самом делене требуется.2.
Имеется возможность перегрузить функцию operator[]() так, чтобы в инструкции присваивания оператор [] можно было располагать как слева, так исправа от оператора =. Для этого возвратите ссылку на индексируемый элемент. Следующая программа иллюстрирует такой подход.ttinclude <iostream>using namespace std;const int SIZE = 5;,class arraytype {int a [SIZE];public:arraytype (} {int i ;for {i=o; i<SIZE; i++) a[i] = i;int Separator [] (int i){ return a [ i ] ;}int main ( )arraytype ob;int i;f o r ( i = 0 ; i<SIZE; icout « o b f i j « " ";cout « "\n";// добавление значения 10 к каждому элементу массиваf o r { i = 0 ; i<SIZE; i-H-}ob[i] = ob[i]+10; // оператор [] слева от оператора =f o r ( i = 0 ; i<SIZE; i+-t-)cout « ob[i] « " ";return 0;В результате работы программы на экран выводится следующее:0 1 2 3 410 11 12 13 14Поскольку теперь функция operator[]() возвращает ссылку на элемент массива с индексом i, то для изменения этого элемента оператор [] можно рас-200_СамоучительC++положить слева в инструкции присваивания.
(Естественно, что как и преждеего можно располагать справа.) Таким образом, с объектами типа arraytypeможно обращаться так же, как и с обычными массивами.3. Перегрузка оператора [] дает возможность по-новому взглянуть на задачуиндексирования безопасного массива. Ранее в этой книге мы рассматривалипростейший способ реализации безопасного массива, в котором для доступак элементам массива использовались функции get() и put(). Перегрузка оператора [] позволит нам теперь создать такой массив гораздо проще.
Вспомните, что безопасный массив — это массив, который инкапсулирован вклассе, и при этом класс обеспечивает контроль границ массива. Такой подход предотвращает нарушение границ массива. Благодаря перегрузке оператора [], работать с безопасным массивом можно так же, как с обычным.Для создания безопасного массива просто реализуйте в функции operator[]()контроль границ. Кроме этого, функция operator[]() должна возвращатьссылку на индексируемый элемент. Например, в представленном ниже примере в предыдущую программу добавлен контроль границ массива, что позволяет при нарушении границ генерировать соответствующую ошибку.// Пример безопасного массива#include <iostream>finclude <cstdlib>using namespace std;const int SIZE = 5;class arraytype {int a[SIZE];public:arraytype () {int i ;for (i=o; i<SIZE; i++) a [ i ] = i;int &operator[] (int i) ;// Обеспечение контроля границ для массива типа arraytypeint fiarraytype: : operator [] (int i)cout « "ХпЗначение индекса ";cout « i « " находится за пределами границ массива.
\пexit (1) ;}return a [i] ;Глава 6. Введение в перегрузку операторов201int main ()arraytype ob;int i;// Здесь проблем нетfor{i=0; i<S!ZE; i++)cout « ob[i] « " ";/* А здесь при выполнении программы генерируется ошибка,поскольку значение SIZE+100 не входит в заданный диапазон */ob[SIZE+100] = 99; // Ошибка!!!return 0;Благодаря контролю границ, реализованному в функции operator[](), привыполнении инструкцииob[SIZE+100] = 99;программа завершится еще до того, как будет повреждена какая-либо ячейкапамяти.Поскольку перегрузка оператора [] позволяет создавать безопасные массивы,которые выглядят и функционируют так же, как самые обычные массивы, ихможно безболезненно добавить в вашу программную среду. Однако будьтевнимательны.
Безопасный массив увеличивает расход ресурсов, что не вовсех ситуациях может оказаться приемлемым. Фактически, именно из-за непроизводительного расхода ресурсов в C++ отсутствует встроенный контрольграниц массивов. Тем не менее, в тех приложениях, в которых желательнообеспечить целостность границ, реализация безопасного массива будет лучшим решением.1. Переделайте пример 1 из раздела 6.6 так, чтобы относительно класса strtypeперегрузить оператор []. Этот оператор должен возвращать символ по заданному индексу. Кроме этого, необходима возможность задавать оператор [] влевой части инструкции присваивания. Покажите, что ваша программа работает.2.