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

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

One c1; Two c2;

c2.Show(c1); return 0;}

void Two::Show(One &c1)

{cout<<c1.s1<<“\n”;}

Бачимо, що порядок оголошень класів змінився (порівняно з попереднім прикладом), оскільки клас, в якому міститься прототип фу- нкції-члена, повинен оголошуватись до класу, який визначає функ- цію-член дружньою.

ЗАВДАННЯ

1.Протестувати приклад з класом One. Розглянути випадок взаємнодружніх класів.

2.Описати клас , дружній до класу Літак, який може змінювати значення окремих полів даних цього класу.(Наприклад, збільшувати кількість палива та боєприпасів). Очевидно, що такий клас може служити моделлю заправки.

3.Вважаючи, що заправка може відбуватись в повітрі, визначити клас

Літак_Заправник , похідний від класу Літак.

8.12 ПЕРЕВАНТАЖЕННЯ ОПЕРАЦІЙ.

8.12.1 Перевантаження операцій. Загальний підхід.

В С++ допускається перевантаження операцій. Така можливість надається при роботі з класами. Для перевантаження операцій використовується ключове слово operator. Щоб перевантажити операцію необхiдно описати функцiю такого виду:

<mun> operator <знак операцiї> (параметри).

Перевантажувати можна всi операцiї, крім “*”, “::”,” sizeof” та “.” . Існує два основних способи визначення перевантажених операцій:

1.перевантаженi операції оголошуються як функцiї, дружнi до кла-

су;

2.перевантаженi операції є членами класу.

Приклад:

class complex{ public:

double re,im; complex(){}

281

complex (double r, double i) {re=r, im=i;}

friend complex operator +(complex,complex); };

void main()

{complex a=complex (1,3.1); complex b=complex (1.2,2);

complex c=b;

 

 

a=b+c;

//(1)

 

c=a+complex (1,2);}

//(2)

complex operator+(complex a,complex b) {complex c;

c.re=a.re+b.re;

c.im=a.im+b.im; return c;}

Замiсть (1)-(2) можемо записати:

а=operator + (b,c) ; c=operator (a,complex(1,2));

Бачимо, що оголосивши перевантажену операцію + як дружню функцію до класу, можемо вільно використовувати значок “+” по відношенню до екземплярів класу. Причому в точках, де стоїть перевантажена операцiя, відбувається виклик відповідної функцiї ( в даному випадку operatop+). Очевидно, що компілятор буде розрізняти + перевантажений та + “звичайний”. Якщо десь буде фігурувати звичайний арифметичний вираз, то ніяких конфліктів не виникне. Іншими словами, перевантажуючи операції ми ніби розширюємо поле їх дії на відповідні екземпляри класів. Відмітимо, що порожній voidконструктор класу complex необхідний для корректного оголошення екземпляра класу c в функції operator+().

Перепишемо приклад таким чином, щоб перевантажена операція була членом класу:

class complex { public:

double re,im; complex(){};

complex (double r,double i) {re=r; im =i;}

complex operator + (complex);} complex complex::operator+(complex a)

{

a.re+=re;

282

a.im+=im; return a;}

Бачимо, що в перевантаженій операції + , на вiдмiну вiд попереднього прикладу, є один формальний параметр типу complex. Другий просто непотрібний. Адже в якості другого параметра виступає вказiвник на об'єкт this, для якого викликається функцiя-член.

Необхідно відмітити, що для перевантажених операцій повинна обов’язково зберiгатись така ж кiлькiсть операндiв, яка була і в базовій операції.

Приклад: class x {

friend x operator - (x); friend x operator - (x,x)

friend x operator - ();// помилка

friend x operator - (x,x,x); // помилка};

В першій та другій перевантаженій операції – все нормально з точки зору кількості операндів: перший мінус-унарний, другий-бінарний. У третій та четвертій є помилка, оскільки не існує базових операцій з відповідною кількістю операндів.

Згадаємо особливість мови С(С++), яка полягає у відсутності можливості роботи з складеними типами даних, як з одним цілим. Наприклад, як зручно в мові програмування Pascal мати вбудований тип string з деяким набором операцій (+,-тощо). Зараз, використовуючи перевантаження, ми можемо самі створити ряд корисних для себе типів. Розглянемо, наприклад, наступний клас:

class string { private:

char value[100]; public: string(){value [0]=0;} string(const char *s)

{strcpy(value,s);}

long GetValue(void) {return atol(value);} friend long operator + (string a, string b); friend long operator - (string a, string b);}; main()

{

string a="1234"; string b="4321";

cout <<"\na+b+6="<<(a+b+6);

cout <<"\na-b+10="<<(a-b+10)<<'\n';}

long operator + (string a, string b)

283

{return (atol(a.value) + atol(b.value)); } long operator - (string a,string b) {return (atol(a.value) - atol(b.value));}

Перепишемо цей приклад так, щоб перевантажені операції були членами класу:

class String { private:

char value[100]; public:

String () {value [0]=0;} String (const char * s);

long GetValue (void) {return atol (value);} long operator + (String b);

long operator - (String a);};

main()

{String a="1234"; String b="4321";

cout <<"\na+b+6="<<(a+b+6);

cout <<"\na-b+10="<<(a-b+10)<<'\n'; return 0;}

String :: String (const char * s) {strcpy (value,s);

}

long String ::operator + (String b) {return (atol(value) + atol(b.value));} long String ::operator - (String a) {return (atol(a.value) - atol(value));}

Таким чином, як бачимо з наведених вище прикладiв, для будь-

якої

перевантаженої

бiнарної операцiї @ вираз aa

@ bb

iнтерпретується як operator @ (aa,bb) чи aa. operator @ (bb)

в залеж-

ностi вiд того, як визначена дана операцiя (функцiя-член чи функцiя друг). Звідси бачимо, що перевантажена функцiя-член не може мати перший параметр-елемент основного типу. Адже 2 @ aa iнтерпретується як 2.operator @ (aa) , що не допускається.

Аналогiчно для будь-якої унарної операцiї @ оператор aa@ чи

@aa iнтерпретується як operator @(aa) чи aa. operator @ ().

8.12.2 Перетворення типів

Програміст повинен сам подбати про перетворення типів при роботі з різнотипними даними, де використовуються його власні типи даних. Операцiї перетворення типiв по замовчуванню при роботi з

284

типами користувача, як правило, не працюють. Виняток становить, мабуть, так зване неявне перетворення типів , про яке буде сказано далі. Для забезпечення роботи з рiзнотипними даними iснує кiлька пiдходiв:

1. Використання перевантажених функцiй з рiзними типами параметрiв

friend complex operator + (complex,complex); friend complex operator + (complex,double);

………………..

complex b(2,2),c(3,3); b=c+20+b;

2. Використання конструкторiв, якi виконують вiдповiднi перетворення типiв. Тобто мається на увазі виклик відповідних конструкторів в ситуаціях, коли замість значення деякого типу по синтаксису повинен бути об’єкт.

Розглянемо описаний вище клас string. В рядках

string a="1234"; string b="4321";

відбувається, фактично, виклик відповідних конструкторів і потім вже присвоєння екземплярам а та в відповідних “абстрактних” об’єктів . Аналогічні виклики відбуваються і у випадках, коли різнотипні дані зустрічаються в деяких арифметичних виразах.

Приклад:

class complex { //...

complex (double f) {re=f; im=0;} };

Можна було б написати конструктор і так: class complex {

// …

complex (double f, double i=0) {re=f, im=i;} };

Тоді в main()-функції можемо написати: complex a(1,2), b(3); a=b+121;

В останньому рядку відбувається неявний виклик конструктора complex(121) і вже потім виконується перевантажена операція додавання для екземплярів класу complex. Якщо конструктор наведеного вище типу не визначений, то можемо в даній ситуації зробити так: a=operator + (b,complex(double(121),double(0)));

Використання конструкторiв має ряд недолiкiв:

285

a)виникають складностi з неявним перетворенням типу вiд типу користувача до основного.

b)не може бути неявного перетворення вiд нового типу до старого без змiни старого типу.

3. Операцiя перетворення типу

Позначимо через Т-iм'я типу, X-деякий клас, тодi функцiя член

X::operator Т();

здiйснює перетворення типу з X в Т. Якщо ми використаємо описаний вище клас string та ініціалізуємо екземпляр класу так:

string myvalye=“1234”;

Тоді рядок long x=myvalue; очевидно, буде помилковим, оскільки в класі не передбачено перетворення рядка в довге ціле.

Вихід з даної ситуації - перевантаження операції перетворення типу. Тобто в класі string необхідно описати функцію виду:

operator long() {return atol (value);}

При роботі з різнотипними даними слід уникати можливих неоднозначностей.

Розглянемо такий приклад: class x{

public:

x(int){};

x(){};

};

class y {

public:

y(int){};

};

class z { public: z(x){};

z(y){};

};

main()

{

z f(1);

}

При оголошенні екземпляру класу z f(1)в функції main() виникає неоднозначність. Згідно оголошення, в якості параметра конструктора класу z має виступати або екземпляр класу x або y. Оскільки тут стоїть одиниця і в класах є конструктори, що дозволять здійснити перетворення від цілого до типу відповідного класу, то таке перетворення

286

повинне бути здійснене. Але до якого з класів буде здійснене перетворення 1: адже вони обидва містять відповідні конструктори? Компілятор відслідковує такого характеру неоднозначності.

Для оптимiзацiї роботи програми в якостi формальних параметрiв перевантажених операцiй краще використовувати посилання.

class matrix {

double m [300][400]; public:

matrix (){};

friend matrix operator + (matrix &,matrix &);};

Це дозволить уникнути копіювання даних, що, можливо, займають значний об’єм пам’яті, в стек.

8.12.3 Перевантаження деяких операцій.

8.12.3.1 Операція індексування масиву.

В С++ можна перевантажувати оператор індексування масиву. Приклад 1:

class MyArray { private:

int v0,v1,v2; public:

MyArray(int _v0, int _v1, int _v2) {v0=_v0;

v1=_v1; v2=_v2;}

int GetVal(unsigned i);

int operator[ ](unsigned i); };

main()

{MyArray pa(10,20,30); for (int i=0, i<=2 i++);

cout<<"pa["<<i<<]="<<pa[i];

}

int MyArray :: GetVal(unsigned i) {switch(i){

case 0:return v0; case 1:return v1; case 2:return v2; default:return -1;}}

int MyArray :: operator [] (unsigned i) {return GetVal(i);}

Як бачимо, в цьому ілюстративному прикладі перевантажується опе-

287

рація індексування масиву так , що її результатом є значення полів v0, v1, v2 чи -1 в залежності від того, яке значення цілого аргументу операції. Після відповідного перевантаження можемо, наприклад, в main-функції написати :

MyArray ekz(1,2,3); cout <<ekz[1];

Тут ekz[1] дає результат перевантаженої операції індексування.В результаті буде надруковане значення поля v1–число 2. Очевидно,

вираз ekz[1] еквівалентний ekz.GetVal(1).

В цьому прикладі в якості індексів виступають цілі числа. Перевантажимо оператор індексування масиву так, щоб в якості аргументу виступали значення типу char.

Приклад 2.

struct vidnoshennia{ char symvol; int znach;

};

class assoc{ public: vidnoshennia* vec;

int max; int free;

assoc(int);

int & operator[](char);

};

assoc:: assoc(int i) {max=i;

vec=new vidnoshennia[i]; free=0;

}

int& assoc::operator[ ](char p)

{

for (vidnoshennia* pp=vec; pp<&vec[max]; pp++) if (pp->symvol==p) return pp->znach; vec[free].symvol=p;

return vec[free++].znach;

}

main ()

{

288

int i=0; char c;

assoc mas(10);

while (cin>>c) mas[c]=i++; for(i=0;i<10;i++) cout<<"symv="<<mas.vec[i].symvol<<"\

znach="<<mas[mas.vec[i].symvol]<<"\n";

}

В цьому прикладі перевантажується операція індексування масиву так, що в якості індексу може виступати символьна змінна а результатом операції є ціле число. Це дозволяє використати в програмі конструкції виду mas[c] та mas[mas.vec[i].symvol]. Тобто, таким спо-

собом ми будуємо відображення множини символів на множину цілих чисел. Причому операція тут перевантажується так, що повертається посилання на відповідне числове поле структури. Такий прийом дозволяє використовувати перевантажену операцію індексування одночасно і для ініціалізації числового поля у випадку, коли елемента з відповідним символьним індексом в масиві структур vec немає.

З прикладу ми бачимо, що iндекси перевантажених масивiв можуть бути довiльними типами даних. В цьому i полягає поняття асоцiативного масиву, тобто за рахунок перевантаженої операцiї iндексування масиву встановлюється взаємно-однозначна вiдповiднiсть мiж множиною "iндексiв" масиву, якi можуть бути даними будь-якого типу та відповідних значень.

8.12.3.2 Перевантаження операції виклику функції.

Синтаксично реалізується наступним чином:

<тип> operator( ) (параметри)

Перевантажена операція виклику функцiї робить об'єкт класу схожим на функцiю, яку можна викликати. При цьому допускається присутнiсть списку параметрiв, якi обчислюються та перевiряються на відповідність компілятором у вiдповiдностi iз звичайними правилами передачi параметрiв.

class FuncClass { int x; public:

int operator () (void); FuncClass(int n) {x=n;}};

int FuncClass :: operator() (void) {return x;}

main ()

{FuncClass object=100;

289

int q=object(); cout <<q; return 0;}

Перевантаження операції виклику функції може бути корисним у випадку, коли об'єкт має домiнуючу властивість. Розглянемо наступний приклад. Перевантажимо оператор індексування масиву так, щоб в якості індексу перевантаженої операції індексування масиву виступав символьний рядок (див. попередній приклад). Причому використаємо цю перевантажену операцію kbit для ініціалізації полів масиву структур vec (див програмну реалізацію нижче). Водночас в класі associ опишемо дружній клас iterator , у якому перевантажимо операцію виклику функції так, щоб вона забезпечувала просування по елементах масиву vec типу vidnosh.

#include<string.h>

#include<iostream.h>

struct vidnosh { char * name; int val;

};

class associ {

friend class iterator; public:

vidnosh * vec; int max;

int free; associ(int);

int& operator [ ] (char *); };

associ::associ(int _max) {max=_max;

vec=new vidnosh[max]; free=0;

}

int& associ::operator[](char* _s)

{

vec[free].name=new char[256]; strcpy(vec[free].name,_s); vec[free].val=free;

return free;

290

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