
- •1 Парадигми та мови програмування
- •1.1 Процедурне програмування
- •1.2 Об'єктне (модульне) програмування
- •1.3 Об'єктно-орієнтовне програмування
- •2 Програмне середовище
- •3 Базові поняття програмування
- •4 Процедурно-орієнтоване програмування
- •4.1 Функції
- •4.2 Вбудовані (inline) функції
- •4.3 Передача параметрів
- •4.4 Обчислення значення функцій
- •4.4. Обчислення значення функції — вихід із функції; особливості повернення та використання іменованого значення, іменованої константи
- •4.5 Рекурсія
- •4.6 Довизначення (overloading) функцій
- •4.6. Довизначення (overloading) функцій
- •4.7 Узагальнені функції (function template)
- •4.8 Непряме використання функцій: указники на функції, їх тип, ініціалізація і присвоєння
- •4.9 Видимість
- •4.10 Тривалість життя об’єктів
- •5 Об'єктне програмування
- •5.1 Класи і об'єкти, члени класів
- •5.2 Інтерфейс класу, реалізація класу; визначення і оголошення класу
- •5.3 Створення і ініціалізація об'єктів, довизначення конструкторів, замовчуваний конструктор, копіювання, поверхневе і глибоке копіювання, ініціалізація і присвоєння, копіювальний конструктор
- •5.4 Cтатичні члени класів і статичні класні функції
- •5.5 Константні об'єкти, константні функції, змінювані члени константних об'єктів (mutable)
- •5.6 Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.7 5.8. Довизначення (overloading) операцій
- •5.8 Клас string: конструктори і присвоєння рядків, операції, відкладене копіювання
- •5.9 Параметризовані класи (class template): визначення шаблону, конкретизація
- •6 Об'єктно-орієнтовне програмування
- •6.1 Ієрархія об’єктів і ієрархія класів
- •6.2 Відкрите, закрите і захищене успадкування, успадкування типу і успадкування реалізації; абстрактні класи
- •6.3 Створення похідних об'єктів
- •6.4 Статичне і динамічне зв’язування
- •6.5 Успадкування із спільного базового класу. Кратне (multiple) успадкування. Віртуальне успадкування, порядок виклику конструкторів
- •7 Екзаменаційні завдання
- •7 Екзаменаційні завдання
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;