Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

chast2

.pdf
Скачиваний:
9
Добавлен:
10.06.2015
Размер:
431.28 Кб
Скачать

____________________________________________________________________________________

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

Перегрузку можно определить почти для всех операций языка С++ за исключением: "." - прямой выбор эл структурированного объекта ".*" - обращение к методу или к полю через указатель на него.

"?:" - условная тернарная операция "::" - указание области видимости

"sizeof"- операция определения размера объекта вбайтах "#","##" - препроцессорные операции

Перегпрузка осуществляется с помощью методов или внешних функций специального вида (функцииоперации) и подчиняется сл правилам:

1.При перегрузке сохраняется арность операции (арность - количество операндов),а также приоритет операций и правила ассоциативности.

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

3.Параметры функции-операции не могут иметь значений по умолчанию.

4.Функции-операции наследуются за исключениемоперации присваивания.

5.Функции-операции не могутбыть статическими.

Функция-операция можетбыть определена как

-метод класса для которого операция переопределяется

-дружественная функция класса

-обычная (свободная) функция.

Указанные варианты функций имеют единый формат определения. <тип>operator<знак операции>(<спецификация параметров функции>){<тело>}

Здесь служебное слово operator является обязательным, а знак операции играет роль имени функции.

Особенности синтаксиса различных вариантов определения функции-операции:

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

-свободная функция не имеет возможности непосредственно обращения к закрытым (private) и защищенным (protected) полям оьъекта поэтому для построения функции-операции как свободной функции в классе обычно определяют специальный открытый (public) метод через который функция сможет получить доступ к необходимому закоытому или защищенному полю.

-при опредедлениифункции-операции как методакласса или дружественнойфункции внутри класса разрешается объявить только прототип функции,а полное определение надо давать только вне объявления класса.

____________________________________________________________________________________

54.Перегрузка операцииприсваивания.Особенности работы сресурсоемкими объектами.

__________________________________________________________________________________

Операция присваиания вызывется всегда когда одному существующему объекту присваивается значение другого.

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

с помощью операции new. В этом сл операция определяется компелятором как поэлементная,а точнее как побитоое копирование объектов (см 2.3.3).

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

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

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

Добавим в определение класса TTochka_v_pole объявление прототипа перегруженной операции присваивания.

const TTochka_v_pole & operator=(const TTochka_v_pole &);//(1)

Спецификатор const применять не обязательно, но целесообразно.

Прежде чем провести полное определение этой функции-операции отметим особенности работы с объектами класса TTochka_v_pole.

В начале разберемся с динамическим полем.

Предположим,что мы должны реализовать следующую логику работы алгоритма: копируемый объект - Р

-если копируемый объект имеет имя,т.е. для поля указателя Fname1 сформирована динамическая область, где хранится строка (имя) то мы должны передать это имя объекту в которй копируем. Здесь возможны 2 варианта действия:

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

-в объекте куда копируем , динамическое поле еще не создано, тогда его надо создать и заполнить копией имеени объекта Р.

-если у копируемого объекта имени нет,то тоже есть 2 варианта действий:

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

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

Реализацию этой логики рассмотрим далее.

Почти все остальные поля обрабатываются простым копированием с помощью операции присваивания. Два статических поля счетчика Fcounter и FLivePoint и обычное поле Fnum требуют особого обращения.

Т.к. поля FCounter и FLivepoint статические, т.е они не принадлежат никакому объекту и по логике нашего алгоритма они при создании любого объекта автоматически обновляются ( это делает конструктор),то нет смысла их копировать обращаясь к ним поимени объекта Р (пустая трата времени).

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

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

const TTochka_v_pole & TTochka_v_pole::operator=(const TTochka_v_pole & P){//(2) if(&P==this)return *this;//(3) предотвращение лишних действий при самоприсваивании // работа с динамическим полем

if(Fname1)delete[]Fname1;//(4) уничтожаем имя копируемого объекта если оно было if(P.Fname1)//(5) у копируемого объекта есть имя

{Fname1=new char[strlen(P.Fname1)+1];//(6) тогда укопирующего объекта создаем динамическое поле strcpu (Fname1,P.Fname1);}//(7) и копируем в него имя объекта Р.

else Fname1=0;//(8)

//далее работаем с остальными полями,за исключением полей FCounter,FLivePoint,Fnum for(int i=0;i<3;i++){FR0[i]=P.FR0[i];FV0[i]=P.FV0[i];}//(9)

for(int i=0;i<3;i++){VminVmax[i][0]=P.VminVmax[i][0];//(10) for(int i=0;i<3;i++)VminVmax[i][1]=P.VminVmax[i][1];}//(11) FMp=P.FMp;//(12)

FMcolor=P.FMcolor;//(13)

Fg=P.Fg;//(14)

for(int i=0;i<20;i++){...Fname2...}//(15) 16-19ст простые поля

return *this;//(20)

}//(21) конец функции-операции присваивания

Пример использования этой функции-операции

Point4=Point2;//(22)

К любой функции-операции можно обратится явно,как к любой обычной функции. Например присваивание значения объекта Point2 объекту Point4 можно было бы осуществить так:

Point4.operator=(Point2);//(22')

Замечания Фактически мы уже изучали модифицируемые операции присваивания для базовых типов языка С++.

например,допустим оператор i+=1,где i целая переменная и которая равносильна i=i+1 Нужно учитывать,что для перегруженных операций такие модификации недопустимы.

_______________________________________________________________________________________

55.Перегрузка унарных операций.

____________________________________________________________________________________

Если функция-операция для унарной операции определяется так:

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

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

Рассмотрим операцию "унарный минус" для объектов класса TTochka_v_pole.

Мы в эту операцию над объектом будем вкладывать следущую симантику:

действие операции на объект должно сводиться к изменению знака прежде всего вертикальной составляющей начальнго вектора скорости точки. Кроме того должны быть пересчитаны поля знначение которых зависят от FV0[2].

Проще всего это сделать обратившись внутри функции-операции к методу Krit_Point. Все остальные поля должны оставаться неизменными. В результате соответствующую функцию-операцию можно определить так:

TTochka_v_pole & TTochka_v_pole::operator-()//(1)

Возвращать надо именно ссылку на объект иначе при возврате просто объекта сработает конструктор копирования и все испортит.

{FV0[2]=-FV0[2];//(2) основная операция смены знака вертикальной составляющей скорости тела Krit_Point();//(3) пересчет характеристик точки

return* this;}//(4) конец фукции-операции унарныйминус

Пример использования унарной операции "унарный минус" совместно с перегруженной операцией присваивания.

Point4=-Point4://(5)

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

-Point4;//(5')

Определим ту же самую унарную операцию как дружественную функцию.

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

//Прототип дружественной функции для перегруженной операции унарный минус.

friend TTochka_v_pole& operator - (TTochka_v_pole&P);//(6)

//Определение дружественной функции вне класса

TTochka_v_pole& operator - (TTochka_v_pole&P);//(7) {P.FV0[2]=-P.FV0[2];//(8)

P.Krit_point();//(9) return P;}//(10)

Аналогично можно перегрузить другие унарные операции за исключением отмеченных в пункте 2.7.1.

Кратко рассмотрим только унарные операции которые имеют 2 формы:

-префиксная

-постфиксная

Для примера рассмотрим ++

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

static int Kol_point_green;//(11) объявление поля в классе

int TTochka_v_pole::Kol_point_green=3;//(12) это внешнее определение статического поля как глобальной переменной и его инициализация.

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

//Прототип перегруженной операции "префексный++" как метод класса.

TTochka_v_pole& operator ++();//(13)

//Внешнее определение функции-операции.

TTochka_v_pole& TTochka_v_pole::operator ++()//(14) {Kol_point_green+=1;//(15)

return *this;}//(16)

ПРимер применения этой операции

++Point4;//(17)

Если вывести значение поля Kol_point_green,то увидим что значение увеличилось на 1.

Прежде чем привести пример в постфиксной форме отметим некоторые парадокся связанные с интерпритацией префиксеой и постфиксной перегруженных операций:

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

Point4++;//(17')

Причем результат будет таким же как и при срабатывании 17.

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

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

//прототип перегруженной операции ++ постфиксный инкримент как метод класса.

Для того чтобы выполнялось правило языка С++ (обеспечивать различимость различных вариантов перегрузки функции) постфиксную функцию-операцию нужно объявит с одним целым параметром.

TTochka_v_pole& operator ++(int);//(18) TTochka_v_pole & TTochka_v_pole::operator ++(int)//(19)

{Kol_point_green+=10;//(20) значение поля наращивается на 10 return *this;}//(21)

Теперь когда мы опредилили обе формы инкриментной операции вызова в 17 и 17' будут давать разные результаты.

Общий важный факт.

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

___________________________________________________________________________________

56.Перегрузка бинарных операций.

__________________________________________________________________________________

Некоторые особенности мы уже отмечали,здесь мы обобщим и дополним материал.

1.В общем сл бинарные операции для объектов могут определяться либо как метод класса с одним параметром

либо как внешнее, в частности дружественная функция,с двумя параметрами.

2.Если фукция-операция для операции @ определена как метод класса то выражение вида x@y//(1)

означает вызов фукции ввиде x.operator@(y)//(2)

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

3.Если фукция-операция для операции @ определена как внешняя,может быть дружественная фукция, то выражение 1 означает вызов фукции ввиде

operator@(x,y)//(3)

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

4.Если в перегруженной операции вида 1 первый параметр по логике программируемого алгоритма должен быть не объектом,то соответствующая функция-операция может быть внешней,в частности дружественной, но не методом класса.

Замечание.

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

5. Среди бинарных операций особое место занимает операция присваивания,кроме того в число особых входят операции: выбор элемента по индексу "[]", выбор элемента по указателю "->". Функции-операции предназначенные для перегрузки этих операций могут быть только нестатическими методами класса,но не внешними функциями.

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

Приведем примеры разработки функции-операциидля перегрузки бинарных операций.

Определение функции-операции для бинарной операции сложения "+".

Если мы имеем дело со сложными объектами, например объектами класса TTochka_v_pole, то помимо потери коммутативности могут появится особенности характерные данному классу.

Рассотрим следующие понимание операции сложения:

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

Кроме того следует учитывать,что при измененииначальных координат изменяются значения полей содержащих траекторные характеристики точек (максимальная высота подъема) следовательно после

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

1. Определение функции-операции "+" как метода класса с параметром объекта.

//Прототип метода.

TTochka_v_pole& operator +(TTochka_v_pole&);//(4)

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

TTochka_v_pole & TTochka_v_pole::operator +(TTochka_v_pole&P1)//(5) {for(int i=0;i<3;i++){FR0[i]+=P1.FR0[i];}//(6)

Krit_point();//(7) наращиваем траекторные характеристики всвязи с изменением начальных координат точек.

return *this;}//(8) возвращаем модифицированный1-ый объект.

Пример использования операции "+" вместе с "=". point3=Point4+Point2;//(9)

2. Определение функции-операции "+" как метода класса с параметром необъектом

//прототип метода

TTochka_v_pole & operator +(odn_mass);//(10)

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

TTochka_v_pole & TTochka_v_pole::operator +(odn_mass A)//(11) {for(int i=0;i<3;i++){FR0[i]+=A[i];}//(12)

Krit_point();//(13) return *this;}//(14)

Пример использования операции

Point3=point4+AR01;//(15)

Здесь AR01 это массив типа odn_mass

3. Определение функции-операции "+" как дружественной функции с двумя параметрами объектами.

//прототип функции

friend TTochka_v_pole & operator +(TTochka_v_pole&P1,TTochka_v_pole&P2);//(16)

//определение функции вне класса

TTochka_v_pole & operator +(TTochka_v_pole&P1,TTochka_v_pole&P2);//(17)

{for(int i=0;i<3;i++){P1.FR0[i]+=P2.FR0[i];}//(18) здесь полагается что поле FR0 является общедоступным P1.Krit_point();//(19)

return P1;}//(20)

Пример использования операции см. в строке 9.

4. Определение фукции-операции "+" как дружественной функции с первым параметром объектом и вторым параметром необъектом.

//прототип функции

friend TTochka_v_pole & operator + (TTochka_v_pole&P1,oden_mass A);//(21) //определение фукции вне класса

TTochka_v_pole & operator +(TTochka_v_pole & P1,oden_mass A)//(22) {for(int i=0;i<3;i++){P1.FR0[i]+=A[i];}//(23)

P1.Krit_point();//(24) return P1;}//(25)

пример использования этой операции см в ст 15.

Рассмотрим операцию "[]" которую можно определить только как метод класса. //прототип метода

double operator[](int i);//(26)обратить внимание на тип возвращаемого результата

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

double TTochka_v_pole::operator [](int i)//(27) {return FR0[i];}//(28)

Пример использования этой операции cout<<"Point3[2]="<<Point3[2]<<endl;//(29)

__________________________________________________________________________________

57.Особенности определения классов сключамиstruct и union.

_________________________________________________________________________________

При определение класса вместа ключа class можно использовать ключи struct и union, т.е. в С++ структуры объединения (см. пункт 6.3 - 6.4 первая часть лекций) рассматриваются как частные случаи классов.

В подавляющем большинстве ситуаций вполне уместно пользоваться ключем class. Одгнако в некотрых ситуациях применение других ключей обеспечат дополнительные удобства.

Здесь рассмотрим только некоторые особенности.

1.При применении ключа struct элементы класса для которых явно не уазан спецификатор доступа по умолчанию имеют доступ public. Соответственно структуру уместно использовать, если все эл класса должны иметь спецификатор доступа public.

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

3.Объединение (union) не может участвовать в иерархии классов выстраеваемого по принципу наследования. Элементы объединения не могут быть объектами тех классов в которых явноопределены конструкторы. Объединение может иметь конструктор и другие методы только нестатические. В безымянном объединении нельзя опредилить метод.

___________________________________________________________________________________

58.Включение классов.

____________________________________________________________________________________

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

Рассмотрим пример.

Определим класс объекта у которого это точки на плоскости в декартовой системе координат.

class point{//(1)

//приватные поля объекта (по умолчанию) double x,y;//(2) координаты точки

public:// далее общедоступные методы класса

point(double x1=0.0,double y1=0.0):x(x1),y(y1){}//(4) конструктор общего вида,играющий роль и конструктора умолчания и конструктора приведения типов.

friend ostream & operator<<(osream & out,point p);//(5) прототип функции-операции для перегрузки операции вывода "<<".

};//(6) конец определения класса point

Конструктор копирования и операцию присваивания для этого класса создает компелятор.

Реализация функции-операции "<<" для класса point.

ostream& operator <<(ostream & out,point p){//(7) out<<"x="<<p.x<<"\ty="<<p.y;//(8)

return out;}//(9) возвращаем поток по ссылке

Здесь ostream это стандартный потоковый класс инкапсулирующий средства для вывода данных простых типов на экран дисплея.

Продемонстрируем теперь "отношение включения классов". Определим класс с именем circle,который будет моделировать окружности. Он будет включать в себя в качестве поля объект класса point, который будет задавать центр окружности.

class circle{//(10)

point cc;//(11) центр окружности double rc;//(12) радиус окружности public://(13)

circle(point c,double r):cc(c),rc(r){}//(14) конструктор №1 в котором центр окружности задан типом point circle(double x,double y,double r):cc(x,y),rc(r){}//(15) это конструктор №2 в котором центр задан отдельными координатами х и у

friend ostream & operator <<(ostream& out,circle с1);//(16) прототип фукции-операции для перегрузки операции "<<"

};//(17) конец определения класса circle

Конструктор копироания и операцию присваиваниясоздает компелятор.

Явно в этом классе определены два конструктора приведения типов, следует обратить внимания на списки инициализации в этих конструкторах.

При выполнении инициализации сс(с) в конструкторе №1 будет вызван конструктор копирования класса point.

При выполнении инициализации сс(х,у) в конструкторе №2 будет вызван явно определенный конструктор умолчания класса point.

Реализация фукции-операции "<<" для класса circle имеет вид.

ostream & operator <<(ostream& out,circle c1){//(18)

out<<"центр окруности: "<<c1.cc<<endl;//(19) операции:обычная, перегруженная,обычная out<<"радиус окружности: "<<c1.rc<<endl;//(20) в данном случае все операции << стандартные return out;}//(21)

Притмеры работы с объектами определенных классов

point pcenter;//(22) определение объекта точка, его поля (координаты точки) будут инициализированы конструктором умолчания

circle kryg1(pcenter,10);//(23) будет использован конструктор №1, радиус=10

circle kryg2(1,2,20);//(24) координаты центра заданы пораздельности x=1 y=2 неявно конструктором №2. cout<<kryg1<<endl;//(25) применяем перегруженную операцию << для объекта типа circle cout<<kryg2<<endl;//(26)

_______________________________________________________________________________________

59.Наследование классов - общие положения.

_________________________________________________________________________________

Рассмотрим отношение наследования классов,но перед этим отметим важную особенность отношения включения классов.

При включении объектов класса А как полей в объектах класса В, последний не получает никаких особых прав на объекты класса А. Класс В может распоряжаться полем объекта ровно в соответствии с правами самого поля-объекта которыми оно, это поле, располагает в классе А.

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

В яз С++ различают 2 вида наследования:

-простое наследование, когда в производном классе указывается только один базовый класс.

-множественное наследование, когда базовых классов 2 или более, и соответственно производный класс по спец правилам может наследовать элементы из нескольких ветвей предков.

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

диаграмма на рис 4.1 иллюстрирует этот вид наследования.

пример иерархии классов при простом наследовании (рис 4.1)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]