Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекцій по С.doc
Скачиваний:
1
Добавлен:
01.04.2025
Размер:
7.72 Mб
Скачать

5.7 5.8. Довизначення (overloading) операцій

Довизначення (overloading) операцій: арифметичні операції (бінарні, унарні, суміщені з присвоєнням), оператори управління пам'яттю, оператор виклику функції, оператор доступу до елементу масиву, оператор доступу за указником, оператор присвоєння,

Визначаючи за допомогою класу новий тип, ми вбудовуємо його в наявне середовище

із своєю системою позначень і понять. Вбудовування це стане найбільш природним

і адекватним, якщо ми збережемо значення вже наявних позначень, довизначивши

та розширивши їх на нові об'єкти. Ось типова схема визначення класу

Class T

{

//Мінімальний набір:

//конструктор

T(t,…,s);

//конструктор копіювання

T(const T&);

//присвоєння глибоким копіюванням

T& operator=(const T&);

//деструктор

~T();

//Можливі операції:

//бінарні арифметичні операції

T operator?( const T&) const;

//вони ж, сполучені з присвоєнням

T& operator?=(const T&);

//приведення типу T до типу S

operator S();

//Особливості реалізації:

//інфіксного декременту (звичайний спосіб)

T operator++();

//постфіксного інкременту (фіктивний параметр)

T operator++(int);

//Можливі непорозуміння:

//обчислення адреси

T* operator&() const;

//кон’юнкція

T operator&( const T&);

//Також можливе довизначення багатьох інших операцій

}

З мінімальним набором засобів у визначеному класі ми вже певною мірою знайомилися.

Присвоєння потребує довизначення лише при необхідності глибокого копіювання,

оскільки поверхневе копіювання передвизначене для кожного класу. Присвоєння,

суміщене з операціями, потребує спеціального довизначення. Розглянемо арифметичні

операції. Для цього визначимо дуже спрощений клас комплексних чисел

class complex

{

double _re, _im;

public:

complex (double r=0,double i=0):_re(r),_im(i){};

complex (const complex& c):_re(c._re),_im(c._im){};

complex operator+ ( const complex&) const;

complex& operator+= ( const complex&);

double re() const {return _re;}

double im() const {return _im;}

};

Проаналізуємо його. Конструктор має замовчування: complex z; надасть об'єкту

z значення 0 ; complex z1(1); створить об'єкт з нульовою уявною частиною. Деструктор

не потрібен, довизначення присвоєння теж.

Обмежимося поки що однією арифметичною операцією додавання. Цілком очевидно,

чого варто сподіватися після виконання наступного фрагменту програми

complex u(1,1), v(2, 2),w;

w=u+v;

Трохи менш зрозумілим буде інший фрагмент

complex u(1,1), w;

w=u+1;

очевидно, що очікується збільшення дійсної частини на 1. Залишилося лише зрозуміти,

як це відбувається. Для цього помітимо, що конструктор комплексного числа задає,

крім всього іншого, перетворення дійсного числа в комплексне, наприклад, complex(1)

перетворює дійсну одиницю на комплексне число, дійсна частина якого одиниця.

Оскільки операції додавання з сигнатурою (complex, double) немає, відбувається

підвищення типу дійсного до комплексного. Насправді присвоєння w=u+1; виконається

як w=u+complex(1) . Тобто щось на зразок

complex * tmp = new complex(1);

w = u+*tmp;

Якби ж ми доповнили б інтерфейс функцією

complex operator+ (double) const;

була б викликана вона і додатковий об'єкт не створювався б.

Те ж саме стосується оператора додавання з присвоєнням

complex u(1,1);

u+=1;

Реалізації операцій досить очевидні

complex& complex::operator+= (double a)

{

_re += a;

return *this;

}

complex complex:: operator+ (double a)const

{

complex res = *this;

return res+=a;

}

Залишилася лише одна проблема, викликана бажанням зберегти комутативність

операції додавання

complex u(1,1), w;

w=1+u;

присвоєння з явним конструктором проходить без проблем

w = complex(1)+u;

Неявне перетворення типів, можливе для другого аргументу, для першого виявилося

неможливим. Чому? Відповідь зрозуміла. Як ми пам'ятаємо, класна функція, додатково

до своїх параметрів містить ще указник на поточний об'єкт this , з яким цей

метод безпосередньо працює. В нашому випадку цього об'єкту немає, його роль

мусив би відігравати тимчасовий об'єкт, а це було б не зовсім коректно, оскільки

об'єкт, що активізує метод, повинен жити довше, ніж період активності методу

(тимчасові об'єкти ліквідуються при виході із функції)

complex * tmp = new complex(1);

w = tmp + u;

Прирівняти в правах перший параметр з другим можна, винісши додавання за межі

класу

complex operator+(const complex& a, const complex& b)

{

complex res=a;

return res+=b;

}

Тепер при необхідності виконати перетворення першого параметру викликатиметься

не класна функція додавання, а утиліта класу.

Ще дві корисні утиліти:

complex operator==( const complex& a, const complex& b)

{

return (a.re()==b.re()) && (a.im()==b.im());

}

ostream& operator<<(ostream &os, const complex& a)

{

os<<"re="<<a.re()<<" im="<<a.im()<<endl;

return os;

}

Детальніше про ввід-вивід у наступному підрозділі. Перейдемо тепер до множення

комплексних чисел. Як відомо, множення простіше виконується над комплексними

числами у тригонометричній формі. Визначимо новий клас

class Tcomplex

{

friend class complex;

double _rho, _phi;

public:

Tcomplex (double r=0,double a=0): _rho(r),_phi(a){};

operator complex ();

Tcomplex operator*(const Tcomplex&) const;

Tcomplex operator*=(const Tcomplex&);

double rho() const { return _rho;}

double phi() const { return _phi; }

};

Все, що тут написано, є очевидним аналогом класу комплексних чисел у алгебраїчній

формі, крім оператора complex() перетворення тригонометрич­ного комплексного

числа в алгебраїчне. Ось його реалізація

Tcomplex::operator complex()

{

complex res(_rho*cos(_phi),_rho*sin(_phi));

return res;

}

Аналогічним перетворенням можна поповнити клас комплексних чисел

Tcomplex::operator complex()

{

complex res(_rho*cos(_phi),_rho*sin(_phi));

return res;

}

додавши його оголошення також до інтерфейсу. Тепер можливі змішані вирази

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

алгебраїчну форму, для множення — навпаки.

class complex

{

double _re, _im;

public:

complex (double r=0,double i=0):_re(r),_im(i){};

complex (const complex& c):_re(c._re),_im(c._im){};

complex operator+ ( const complex&) const;

complex& operator+= ( const complex&);

double re() const {return _re;}

double im() const {return _im;}

operator Tcomplex();

bool operator!= (const complex& a)

{return !(*this==a);}

complex operator- ( const complex&) const;

complex& operator-= ( const complex&);

};

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

одне іншому. Яку з них обирати, залежить від додаткових вимог до задачі.

Тепер розглянемо нечисловий приклад.

class MenuItem

{

private:

float price;

string name;

public:

MenuItem( float, string);

MenuItem( float, char[]);

MenuItem(const MenuItem&);

float getPrice();

};

Припустимо, що було зроблено замовлення:

MenuItem chicken( 8.99, ”Київські котлети”);

MenuItem wine( 2.99, ”Каберне”);

MenuItem pie(5.98,”Київський торт”);

float total;