Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Взаимно дружественные классы↑ Стр 1 из 12Следующая ⇒ Содержание книги
Поиск на нашем сайте
Друзья Одно из основных преимуществ ООП — инкапсуляция данных в классе. Подобно многим правилам в жизни и в программировании, концепция скрытия данных существует для того, чтобы ее нарушали. В C++ можно обойти правила инкапсуляции с помощью друзей. хотя при этом вы рискуете благополучием вашей программы. Объявление друзей класса подобно вручению приятелю дубликатов ключей от вашей квартиры, И если вы удалились на уикэнд, не удивляйтесь, когда, вернувшись, вы увидите, что ваш знакомый прикорнул на диване, а холодильник основательно выпотрошен. Классы в C++ позволяют объявлять два вида друзей. Один класс как единое целое может быть другом другого класса, или же другом может объявляться только одна функция. Если друзья имеют брата-близнёца в C++, так это оператор goto. Подобно goto, друзья позволяют вам пренебречь правилами, призванными помочь вам написать надежный код. Не подумайте, что следующий раздел поощряет использование друзей. Асы в программировании на C++ пользуются друзьями только в случае крайней необходимости. Дружественные классы В классе можно объявить другой класс дружественным. Один класс (в котором объявляется друг) дает другому классу (другу) возможность доступа ко всем закрытым и защищенным членам первого класса. Открытые члены всегда доступны, т.о., не надо объявлять один класс другом другого, чтобы дать ему доступ к открытым членам последнего. Обычно дружественные классы используются, когда двум классам, не связанным отношением родства, необходим доступ к закрытым или защищенным секциям классов.
Пример: объявили следующий класс: class AClass { private: double value; // Закрытый член класса public: AClass() { value = 3.14159; } }; в классе не предусмотрены средства просмотра значения value объекта класса. Закрытый член находится в полной безопасности Класс AClass содержит один закрытый член value типа double. Конструктор класса присваивает этому члену значение 3.14159. За исключением этого действия, в классе не предусмотрены даже средства просмотра значения value объекта класса. Закрытый член находится в полной безопасности, как медвежонок под защитой мамаши-медведицы. Далее, предположим, что объявили еще один класс, содержащий членом объект класса AClass: class BClass { private: AClass anObject; // член объекта класса AClass // закрыт в классе BClass public: void ShowValue(void) { cout << anObject.value; }//??? // объявление не скомпилируется, //т.к. член value закрыт в классе //AClass и только функции-члены //AClass имеют к нему доступ.
Член anObject типа AClass закрыт в классе BClass. Функция-член ShowValueO пытается отобразить value из anObject. Однако объявление не скомпилируется, поскольку член value закрыт в классе AClass и только функции-члены AClass имеют к нему доступ. Замена в классе AClass спецификатора доступа private на protected для члена value не решает проблему. Защищенный член доступен в классе, в котором он объявлен, и в любых производных из него классах. Несмотря на то что класс BClass обладает объектом типа AClass, два этих класса не связаны между собой отношением родства, и члены BClass не имеют доступа к закрытым и защищенным членам AClass. Можно сделать член value открытым в классе AClass, но это сделает value доступным и во всех операторах. Лучшее решение — сделать класс BClass другом класса AClass. Объекты BClass будут иметь доступ к value и всем другим закрытым и защищенным членам, а операторы вне двух классов будут лишены возможности доступа к запрещенным частям класса. Для этого следует указать ключевое слово friend внутри класса, к которому необходимо получить доступ в другом классе. В данном примере класс BClass получит доступ к закрытому члену value внутри AClass. Таким образом, для того, чтобы класс BClass получил доступ к этим закрытым данным в классе AClass, следует первый класс объявить дружественным второму. Пример: новое объявление класса AClass: class AClass { friend class BClass; //класс BClass – друг класса AClass private: double value; //доступен членам классов AClass и BClass public: AClass() { value = 3.14159;} }; Единственное отличие от предыдущего объявления — строка friend class BClass, дающая указание компилятору предоставить классу BClass доступ к защищенным и закрытым членам класса AClass. Другие операторы в других классах и в основной программе, как и раньше, не имеют доступа к объявленным закрытыми и защищенными членам класса AClass. Только BClass может частным образом проникнуть в закрытую "комнату" AClass. Можно объявить любое число классов друзьями. Единственное ограничение — ключевое слово friend д.б. внутри объявления класса.
Полезно запомнить еще некоторые факты. • В классе д.б. перечислены все его друзья. • Класс, в котором содержатся закрытые и защищенные данные и в котором другой класс объявлен другом, дает этому другу специальный доступ к обычно скрытым членам объявленного класса. Класс никогда не может объявить сам себя другом другого класса. • Дружественный класс может объявляться до или после класса, которому он друг. Порядок объявлений не играет роли, однако обычно дружественные классы объявляются последними, чтобы встраиваемые функции класса могли обращаться к закрытым или защищенным частям другого класса. • Производные от дружественных классы не наследуют специального доступа к первоначально закрытым и защищенным членам исходного класса. Только специально заданные классы-друзья имеют на это разрешение. Производный класс м.б. другом своего базового класса, хотя в этом случае использование защищенных членов базового класса и предоставление дружественного доступа к его скрытым членам приводит к аналогичным результатам.
Взаимно дружественные классы Два класса могут объявить себя друзьями, предоставив одному классу доступ к закрытым и защищенным членам другого класса. Это также устранит барьеры, препятствующие доступу к скрытым членам класса. Часто возникающая необходимость объявления одних классов друзьями других свидетельствует о плохо продуманной иерархии классов. Лучше, чтобы как можно больше классов были друг другу "чужаками". Чтобы два класса стали взаимно дружественными, в каждом из классов следует указать другой дружественным, что предоставит доступ каждому из них к закрытым и защищенным членам другого класса. Пример: Чтобы AClass и BClass стали взаимно дружественными, их следует объявить таким образом: // предварительное неполное //объявление класса class BClass; class AClass { friend class BClass; }; class BClass { friend class AClass; }; Все функции-члены в каждом из классов теперь имеют доступ к закрытым и защищенным членам объекта другого класса. Если в первом классе имеется ссылка по имени на второй класс, например в параметре функции-члена, то может понадобиться предварительное неполное объявление класса, как это сделано в данном случае для BClass. Это позволит в AClass объявлять данные-члены и параметры функций-членов типа BClass несмотря на то, что полное объявление BClass следует позже.
Дружественные функции Дружественные ф-ции похожи (однако менее обременительны) на дружественные классы. Объявление функции другом класса открывает ей доступ к закрытым и защищенным членам объекта класса. Дружественные функции могут быть обычными функциями C++ или членами класса. Обычно дружественная функция объявляется с параметрами-классами, с которыми она "водит" дружбу. Внутри дружественной функции операторы имеют доступ к обычно скрытым членам объекта класса, переданного аргументом функции. В листинге 4.2 демонстрируется объявление и использование типичной дружественной двум классам функции. В FRIENDFN объявляются два класса, One и Two. Неполное объявление класса в строке 3 позволяет ссылаться в One на Two. В строках 6 и 14 объявляется дружественная функция с именем ShowO. Поскольку в каждом классе функция ShowO объявляется дружественной, операторам ShowO разрешается доступ к закрытым и защищенным членам классов One и Two. (Классы, приведенные здесь, не имеют закрытых секций, но даже если бы они их имели, дружественная функция все равно имела бы доступ к членам этих секций.) Листинг 4.2. FRIENDFN.CPP (использование дружественных двум классам функций)
1: «include <iostream.h> 2: class Two; // Неполное объявление класса 3: class One { // дружественная ф-ция с именем Show() 4: friend void Show(0ne &c1, Two &c2); 5: private: 6: char *s1; // Доступен классу One и функции Show() 9: public: 10: One() { s1 = "Testing "; } 11: }; 12: 13: class Two { 14: friend void Show(0ne &c1, Two &c2); 15: private: 16: char *s2; // Доступен классу Two и функции Show 17: public: 18: Two() { s2 = "one, two, three"; } 19: }; 20: Main() 22: { 23: One c1; 24; Two c2; 25: 26: Show(c1, c2); 27: return 0; 28: } 29: //Show() дружественна обоим классам 30: void Show(0ne &c1, Two &c2) 31: { 32: cout «c1.s1 «c2.s2 «'\n'; 33: } //операторы внутри Show() имеют //доступ к защищенным и закрытым //членам объектов классов-аргументов, //переданных функции Функция Show() объявлена с двумя ссылочными параметрами cl и с2 двух типов классов. Поскольку функция ShowO дружественна обоим классам, операторы внутри ShowO имеют доступ к защищенным и закрытым членам объектов классов-аргументов, переданных функции. Например, в реализации функции ShowO (строки 30-33) отображаются значения строк, адресованных указателями si и s2. Поскольку функция' ShowO дружественна обоим классам, она может ссылаться непосредственно на данные-члены si и s2, закрытые в своих классах. Другие "недружественные" функции не имеют такого доступа к скрытым членам этих классов.
Дружественные функции-члены Обычно в классе объявляется дружественной функция-член другого класса. Эта ф-ция-член имеет доступ к закрытым и защищенным членам класса, в котором она объявлена дружественной. Порядок объявления двух классов: класс, в котором содержится прототип ф-ции-члена, должен объявляться до класса, указывающего ф-цию-член дружественной.
ПРИМЕР: Листинг 4.3. FRIENDMF.СРР (использование дружественных функций-членов) include <iostream.h> // Неполное объявление класса class One; class Two { private: // Доступен членам класса Two char *s2; public: Two() { s2 = "one, two, three"; } //!! Сначала прототип ф-ции-члена void Show(0ne &c1); } class One { //!!потом дружественная ф-ция-член Show friend void Two::Show(0ne &d); private: // Доступен в классе One и в Two::Show char *s1; public: 0ne() { s1 = "Testing "; }; Main() { One c1; Two c2; c2.Show(c1); return 0; } void Two::Show(One &c1) { cout << c1.s1 <<s2 << '\n';} //Ф-ция Show - член класса Two, имеет // доступ ко всем членам класса Two. Как и в модуле FRIENDFN (листинг 15.2), в новой программе FRIENDMF объявляется два класса, One и Two. В классе One объявляется аналогичная дружественная функция-член (строка 14) с помощью имени класса и оператора разрешения области видимости (Two::), чтобы указать компилятору местонахождение функции ShowO. Порядок объявления двух классов изменился по сравнению с приведенным в предыдущем листинге на обратный, поскольку класс, в котором содержится прототип функции-члена, должен объявляться до класса, указывающего функцию-член дружественной. Для класса One, объявляющего дружественной функцию Two::Show(), объявление класса Two уже должно быть доступно компилятору. Еще одно отличие состоит в способе доступа к закрытым данным обоих классов в функции ShowO (строки 30-33). Объявлен только один ссылочный параметр One &cl. Поскольку функция ShowO — член класса Two, она имеет непосредственный доступ ко всем членам класса Two. Выражение cl.sl в операторе вывода в поток в строке 32 не является недозволенным, так как функция ShowO дружественна классу One, которому принадлежит закрытый член si. Функция-член ShowO может ссылаться на s2 непосредственно, поскольку этот член принадлежит классу Two, в котором объявлена ShowO. Поскольку функция ShowO — член класса Two, в ней определен указатель this, ссылающийся на объект класса, для которого была вызвана функция-член. Поэтому в программе должен объявляться объект класса Two (строка 24), затем вызываться функция ShowO для этого объекта (строка 26). Перегрузка операторов Тема перегрузки операторов едва ли не стала предметом суеверного поклонения. Перегрузка операторов позволяет определять действия для объектов класса в выражениях, использующих обычные операторы, такие как сложение (+) или вычитание (-). Пример: Можно объявить класс, подобный TClass, а затем и несколько объектов этого класса с соответствующим образом переопределенными операторами: TClass c1, с2, сЗ;
Можно использовать эти объекты в выражениях вида: c3 = с1 + с2;
Для лучшего понимания сути перегрузки операторов полезно провести ревизию того, что вам известно об операторах вообще. Конечно, обычный оператор знак плюс (+) суммирует два значения. Знак минус (—) осуществляет операцию вычитания. (+)(—) Эти и другие символы называются бинарными операторами, т.к. им необходимы два аргумента. Другие, например, оператор отрицания (!), — унарные, им требуется только один аргумент. Еще один пример — унарный минус (-). Выражение -count меняет знак значения count. Перегрузка операторов дает возможность добавлять к встроенным типам данных, используемых операторами C++, новые типы. Перегрузка операторов объявляется так же, как и обычная дружественная функция или функция-член класса.
Main() { TStrOp a = "1234"; TStrOp b = "4321"; cout << "\nValue of a == " <<a.GetValue(); cout <<"\nValue of b == " <<b.GetValue(); cout <<"\na + b + 6 == "<< (a+b+6)<<"\n"; cout <<"\na - b + 10 == " << (a-b+10)<<"\n"; getch(); return 0; } TStrOp:: TStrOp(const char *s) { strncpy(value, s, 11); value[11] = 0; } long TStrOp::operator+(TStrOp b) { return (atol(value) + atol(b.value)); } long TStrOp::operator-(TStrOp b) { return (atol(value) - atol(b.value)); } //эти функции-члены получают //указатель this, ссылающийся на //объект, для которого они были //вызваны. След-но, функциям //необходим всего лишь один //параметр, а не два, как ранее. //Для сложения двух строковых //значений operator+() суммирует //длинные целые значения, //эквивалентные выражениям // this->value и b.value. Рассмотрим перегруженные операторные функции-члены, объявленные в строках 12-13 и реализованные в строках 32-33. Поскольку эти функции — члены класса TStrOp, они уже имеют доступ к защищенным и закрытым членам класса, таким образом, не надо их указывать друзьями класса. Кроме того, эти функции-члены получают указатель this, ссылающийся на объект, для которого они были вызваны. Следовательно, функциям необходим всего лишь один параметр, а не два, как ранее. Для сложения двух строковых значений operator+() суммирует длинные целые значения, эквивалентные выражениям this->value и b.value. Функциям-операторам и функциям-членам необходимо соответствующее число параметров. Вы не сможете объявить operator+О в строке 12 следующим образом: long operator+(TStrOp a, TStrOp b); //??? Эта строка не скомпилируется, поскольку operator+О — член класса TStrOp и как функция-член получает указатель this, ссылающийся на объект класса. С учетом указателя this и аргументов а и b всего функция имеет три параметра, а перегруженному бинарному оператору, подобному operator+О, необходимы только два. Для сложения двух объектов класса TStrOp функция operator+О суммирует *this.value и b.value (строка 37). Ей не нужен третий параметр. Функция-член operator-О также получает указатель this и, следовательно, нуждается только в одном аргументе для перегрузки бинарного оператора вычитания (строки 40-43). Main() { TStrOp a = "1234"; TStrOp b = "4321"; cout << "\nValue of a == " <<a.GetValue(); cout <<"\nValue of b == " <<b.GetValue(); cout <<"\na + b + 6 == "<< (a+b+6)<<"\n"; cout <<"\na - b + 10 == " << (a-b+10)<<"\n"; cout <<"\n-a== "<< (-a)<<"\n"; getch(); return 0; } TStrOp:: TStrOp(const char *s) { strncpy(value, s, 11); value[11] = 0; } long TStrOp::operator+(TStrOp b) { return (atol(value) + atol(b.value)); } long TStrOp::operator-(TStrOp b) { return (atol(value) - atol(b.value)); } long operator-(TStrOp a){ return (-atol(a.value)); }
Новая функция, не являющаяся членом класса, не получит указателя this. Для реализации новой функции перегрузки унарного минуса, не являющейся членом класса, добавьте следующие строки после функции main(): long operator-(TStrOp a) return - atol(a.value); Поскольку унарный- оператор "минус" — друг класса, оператор return имеет непосредственный доступ к закрытому члену параметра а, имеющему тип класса TStrOp. Теперь функция закончена, и компилятор может вычислить выражения, использующие унарный минус и объекты этого типа класса. Например, добавьте следующий оператор в функцию main О (лучше всего после строки 20): cout «"\n-a == " << -а;
Скомпилируйте и запустите на выполнение модифицированную программу. Как вы видите, выражение -г заменено на операцию отрицания длинного целого представления строкового члена а. Вы научили C++ выполнять операцию отрицания над длинными целыми значениями, представленными в строковой форме! можно объявить перегруженные унарные операторы функциями-членами. Беря за основу первоначальную копию STROPS2.CPP, добавьте следующее объявление над строкой 14 (перед закрывающей скобкой класса TStrOp): long operator-(void); #include <iostream.h> #include <stdlib.h> #include <string.h> #include <conio.h> class TStrOp { private: char value[12]; public: TStrOp() { value[0] = 0; } TStrOp(const char *s); long GetValue(void) { return atol(value); } long operator+(TStrOp b); long operator-(TStrOp b); long operator-(void); }; Main() { TStrOp a = "1234"; TStrOp b = "4321"; cout << "\nValue of a == " <<a.GetValue(); cout <<"\nValue of b == " <<b.GetValue(); cout <<"\na + b + 6 == "<< (a+b+6)<<"\n"; cout <<"\na - b + 10 == " << (a-b+10)<<"\n"; cout <<"\n-a== "<< -a<<"\n"; getch(); return 0; } TStrOp:: TStrOp(const char *s) { strncpy(value, s, 11); value[11] = 0; } long TStrOp::operator+(TStrOp b) { return (atol(value) + atol(b.value)); } long TStrOp::operator-(TStrOp b) { return (atol(value) - atol(b.value)); } long TStrOp::operator-(void){ return (-atol(value)); // аналогично -atol(this->value) } Это объявление функции-члена похоже на объявление предыдущей дружественной унарной функции. Поскольку все функции-члены получают указатель this, новая функция-член operator-() объявляется без параметров и оперирует непосредственно длинным целым эквивалентом значения this->value. Это демонстрируется в реализации функции, которую вы можете вставить после функции main(): long TStrOp::operator-(void) return -atol(value); // аналогично -atol(this->value) Перегруженная функция-член возвращает длинное целое значение value с обратным знаком. Его можно использовать в выражениях, подобных следующему, которые можно вставить в функцию main(): cout «"\п-а == " << -а; Преобразование типов можно определить свои собственные правила преобразования типов данных для автоматического преобразования объектов в другие типы. Например, предположим, что объявили и инициализировали объект класса TStrOp из STROPS2.CPP (листинг 15.5) следующим образом: TStrOp myValue = "9876"; ... Класс TStrOp запоминает строку, которая может использоваться как эквивалент длинного целого значения в операциях сложения и вычитания. можно логически попытаться присвоить объект myValue переменной длинного целого типа: long х = myValue; //??? Конечно, этот оператор не скомпилируется, поскольку в классе TStrOp не предусмотрено преобразование строки в длинное целое значение. Решение этой проблемы заключается в использовании перегрузки операции преобразования типов для преобразования объекта класса TStrOp к другому типу данных. Операторы преобразования принимают форму оператора TYPE, где TYPE — тип данных, к которому необходимо преобразовать объект класса. Например, для задания правила преобразования типа объектов класса TStrOp в длинное целое надо вставить следующую встраиваемую функцию внутрь открытой секции класса: operator long() { return atol(value); } #include <iostream.h> #include <stdlib.h> #include <string.h> #include <conio.h> class TStrOp { private: char value[12]; public: TStrOp() { value[0] = 0; } TStrOp(const char *s); long GetValue(void) { return atol(value); } long operator+(TStrOp b); long operator-(TStrOp b); long operator-(void); //оп-ор преобразования типов operator long(){return atol(value);} }; Main() { TStrOp a = "1234"; TStrOp b = "4321"; long x=a; cout << "\nValue of a == " <<a.GetValue(); cout <<"\nValue of b == " <<b.GetValue(); cout <<"\na + b + 6 == "<< (a+b+6)<<"\n"; cout <<"\na - b + 10 == " << (a-b+10)<<"\n"; cout <<"\n-a== "<< -a<<"\n"; cout <<"\n x== "<< x<<"\n"; getch(); return 0; } TStrOp:: TStrOp(const char *s) { strncpy(value, s, 11); value[11] = 0; } long TStrOp::operator+(TStrOp b) { return (atol(value) + atol(b.value)); } long TStrOp::operator-(TStrOp b) { return (atol(value) - atol(b.value)); } long TStrOp::operator-(void){ return (-atol(value)); } Подобное использование оператора ключевого слова специально обеспечивается в C++ для создания новых правил преобразования типов. С новым правилом появилась возможность присвоить объект типа TStrOp переменной длинного целого типа, передав объект TStrOp в качестве параметра функции, возвращающей длинное целое, и т.д. Main() { clrscr(); PArr pa(10, 20, 30, 40); for (int i=0; i <= 3;i++) cout<< "pa[" <<i <<"] == "<<pa[i] <<"\n"; getch(); return 0; } Оператор вызова функции Перегрузка вызова функции operator() делает объект класса похожим на функцию, которую можно вызвать. Перегруженная функция operator() может возвращать значения заданных типов или вовсе ничего не возвращать. В ней также могут объявляться параметры. Она не может быть статическим членом класса. Пример4.20 класса с перегруженным оператором вызова функции, возвращающим целочисленное значение: #include <iostream.h> #include <conio.h> class TAClass { int х; public: //Перегруженная функция-член int operator() (void); TAClass (int n) { x = n; } }; //В классе TAnyClass перегружается //оператор вызова функции с //помощью объявления int //operator()(void); //Можно заменить void списком //необходимых параметров. //Перегруженная функция-член int TAClass::operator()(void) {return x; } В этом примере функция operatorO возвращает целочисленные значения члена х объекта TAClass. main() {clrscr(); TAClass object = 100; int q = object(); // вызов функции! //выполняется оператор // object. Operator()(); cout << q; getch(); return 0; }
Второй оператор похож на вызов функции с именем objectO, но на самом деле выполняется оператор object. Operator()();.
TAClass(int xx, int yy) { x = xx; y = yy; } TAClass* operator->(); //Функция-член operator->() //возвращает объект, ссылку или //указатель на объект класса. //В данном случае возвращает //указатель на класс TAClass. int GetX(void) { return x; } int GetY(void) { return y; } }; TAClass* TAClass:: operator->() { cout << "\n Доступное данное: "; //указатель, ссылающийся на объект, //для которого была вызвана //функция-член return this; } Main() { TAClass t(100, 200); cout << t->GetX() << '\n'; cout << t->GetY() << '\n'; // На самом деле вып-ся так: //cout«(t.operator->())->GetX() //«'\n'; //cout«(t.operator->())->GetY() //«'\n'; getch(); return 0; }
Результат: Accessing member: 100 Accessing member: 200
Функция-член operator->() должна возвращать объект, ссылку или указатель на объект класса. В данном I случае она возвращает указатель на класс TAnyClass. Оператор реализуется следующим образом: I TAClass* TAClass::operator->() { cout «"\nAccessing member: "; return this; } В данном примере демонстрируется использование перегрузки оператора -> в качестве отладочного трюка [ Перегруженный оператор выводит короткое сообщение перед тем, как вернуть указатель this, ссылающийся на объект, для которого была вызвана функция-член. В основной программе перегруженный оператор может применяться для объектов класса TAClass следующим образом: main() { TAnyClass t(100, 200); cout «t->GetX() «'\n'; cout «t->GetY() «'\n' return 0; } В двух операторах вывода в поток используется перегруженный оператор -> для доступа к функциям-членам GetXO и GetY() объекта t класса TAnyClass, На самом деле, эти операторы выполняются так, как приведенные ниже: cout «(t.operator->())->GetX() «'\n'; cout «(t.operator->())->GetY() «'\n'; Поэтому программа отобразит метки перед значениями, возвращаемыми функциями GetXO и GetY(): Accessing member: 100 Accessing member: 200
Int operator--() { return --x; } Int operator--(int) { return x--;} int GetX(void) { return x;} }; main() { TAClass t(100); clrscr(); cout<<"t=="<<t.GetX() <<";++t==" << ++t<<"\n"; cout<<"t=="<<t.GetX()<< ";t++==" << t++<<"\n"; cout<<"t ==" << t.GetX() << ";--t==" << --t<<"\n"; cout<<"t=="<<t.GetX()<<";t--==" << t--<<"\n"; getch(); return 0; } Результат: t=101; ++t=101 T=102; t++=101 T=101; --t=101 T=100; t--=101 Другие вопросы, касающиеся перегрузки операторов Получив некоторое представление о перегрузке операторов, необходимо иметь в виду следующие факты. • C++ "не понимает" семантики перегруженного оператора. Вы должны обеспечить осмысленные функции перегрузки операторов. • C++ не может выводить сложные операторы из простых. В классе, в котором объявлены функции перегрузки операторов operator*() и operator=(), C++ не сможет вычислить выражение а *= b до тех пор, пока вы не перегрузите operator*=(). • Никогда не изменяйте синтаксис перегруженных операторов. Бинарные операторы должны оставаться бинарными, унарные — унарными. Например, нельзя создать унарное деление, поскольку такой встроенной возможности в C++ не существует. • Нельзя изобретать новые операторы. Вы можете перегружать только операторы, перечисленные в табл. 15.1. • Нельзя перегружать символы препроцессора # и ##. ПРЕДУПРЕЖДЕНИЕ Никогда не вызывайте функцию free() для освобождения памяти, выделяемой с помощью оператора new, и никогда не вызывайте mallocO для выделения памяти, освобождаемой позднее с помощью delete. Использование способов управления распределением памяти ANSI С и C++ вперемежку может в некоторых случаях сработать, однако программа, скорее всего, будет завершаться аварийно, если new и delete перегружаются в классе или если Borland изменит внутреннюю реализацию этих операторов в будущих версиях компилятора. Перегрузка оператора new Вы можете перегружать new точно так же, как и операторы, подобные + и =. Перегрузка new в объявлении класса указывает компилятору, что отныне на вас лежит ответственность за поддержку запросов выделения памяти для объектов этого класса. Для перегрузки new следует использовать прототип функции вида void * operator new(size_t size);. В дальнейшем обращения к оператору new для выделения памяти объектам класса будут перенаправлены перегруженнойфункции. Функция должна возвращать адрес области памяти, выделенной объекту. Если необходимого свободного пространства нет, функция должна возвращать null (нуль). В листинге 4.7 приводится простой, но полный пример перегрузки оператора new для выделения памяти объектам. Вместо кучи программа запоминает их в глобальном буфере. Можно применить аналогичный трюк для выделения памяти объектам в других местах, например на диске или другом запоминающем устройстве. ЗАМЕЧАНИЕ В строке 6 OVERNEW.CPP приводится команда #pragma warn -aus. Она предписывает компилятору выключить предупреждение о том, что в программе не используются переменные, объявленные исключительно в демонстрационных целях. Функция перегрузки оператора new, объявленная в строке 13 и реализованная в строках 38-48, проверяет наличие пространства в глобальном буфере. Если его нет, функция возвращает 0, что является основанием для возвращения оператором new значения null. (В программе не проверяется это условие, но, конечно, вы должны проверять его в реальных приложениях.) Если свободное пространство есть, глобальный индекс увеличивается на размер запрашиваемой памяти, который передается функции в параметре size. Затем функция возвращает адрес выделяемого участка памяти. Листинг 4.7. OVERNEW.CPP (перегрузка new) //4_7.cpp #include <iostream.h> #include <conio.h> // Следующая директива "pragma" выключает //предупреждение компилятора о том, что // в программе объявляются и не используются //демонстрационные переменные #pragma warn -aus class BNew { private: int x; public: BNew(); // Функция перегрузки оператора new void * operator new(size_t size); }; char buf[512]; int index=0; main() { cout << "\nCreating local instance"; BNew b1; cout << "\nAllocating space via new"; BNew *b2 = new BNew; BNew *b3 = new BNew; BNew *b4 = new BNew; BNew *b5 = new BNew; getch(); return 0; } BNew::BNew() { cout << "\n constructor"; x = index; } void *BNew::operator new(size_t size) { cout << "\n lnside overloaded new. Size = "<< size; if (index >= 512 - sizeof(BNew)) {getch(); return 0;} else { int k = index; index += sizeof(BNew); return &buf[k]; }} Перегрузка delete Оператор delete — обратная сторона медали распределения памяти. Можно перегрузить оператор delete для отслеживания удаления объектов, адресованных указателями. Прототип функции перегрузки оператора delete должен иметь вид void operator delete(void *p); где р ссылается на удаляемый объект. В качестве альтернативы можно объявить функцию следующим образом: void operator delete(void *p, size_t size); В соответствии с вторым объявлением C++ будет дополнительно передавать функции число байтов в удаляемом объекте. Для добавления функции перегрузки оператора delete в класс BNew в модуле OVERNEW (листинг 4.7) объявить функцию-член в открытой секции класса: void BNew::operator delete(void *p) cout << "\nDeleting object at " «p; Оператора вывода отображает адрес каждого удаляемого объекта. Поскольку объекты в программе не запоминаются в куче, перегруженный оператор delete на самом деле не освобождает никакой памяти. также потребуется несколько операторов для удаления нескольких объектов. Вставьте следующие строки в функцию mainO непосредственно перед оператором return: delete Ь2; delete ЬЗ; delete Ь4; delete Ь5; //4_7А.cpp #include <iostream.h> #include <conio.h> class BNew { private: int x; public: BNew(); void * operator new(size_t size); void operator delete(void *p); }; char buf[512]; int index=0; main() { clrscr(); cout << "\nCreating local instance"; BNew b1; cout << "\nAllocating space via new"; BNew *b2 = new BNew; BNew *b3 = new BNew; BNew *b4 = new BNew; BNew *b5 = new BNew; delete b2; delete b3; getch(); return 0; } BNew::BNew() { cout << "\nlnside constructor"; x = index; } void *BNew::operator new(size_t size) { //cout << "\nlnside overloaded new. Size = "<< size; //cout << "\n index = "<< index; if (index >= (512 - sizeof(BNew))) {getch(); return 0;} else { int k = index; index += sizeof(BNew); //cout << "\n k = "<< k; return &buf[k]; } } void BNew::operator delete(void *p) { cout << "\n delete "<< p; }
Этот пример приведен только для демонстрации, поскольку в полноценном приложении перегруженный оператор delete должен освобождать удаленные блоки памяти для последующего использования их перегруженным new. ЗАМЕЧАНИЕ Чтобы использовать средства управления памятью C + + для выделения пространства в куче для объектов, в которых перетружен оператор new, поставьте перед оператором двойное двоеточие. Например, и строке BNew *х =::new BrandNew игнорируется перегруженный в объектах класса BNew оператор new. Аналогично,::delete служит для обращения к оператору delete, используемому по умолчанию. Установка обработчика ошибок оператора new Обычно, если new не может выполнить запрос выделения памяти, оператор возвращает null. Для того чтобы изменить действия, предпринимаемые по умолчанию в этом случае, в C++ следует присвоить адрес функции-обработчика ошибок указателю _new__handler, определенному следующим образом:
|
||||
Последнее изменение этой страницы: 2016-08-01; просмотров: 827; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.226.82.90 (0.014 с.) |