
- •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) операцій
Хотілося б вирахувати суму до сплати як
total = chicken + wine + pie;
Для цього потрібна позакласна
float operator+(
const MenuItem& item1, const MenuItem& item2)
{
return(item1.getPrice() + item2.getPrice() );
}
або класна
float MenuItem :: operator+(const MenuItem& item )
{
return( GetPrice() + item.GetPrice() );
}
Спробуємо розібратися, скільки довизначень оператора додавання необхідно тепер.
Зокрема ясно, як буде опрацьовано вираз
chicken + houseWine
але неясно, що робити з виразом
chicken + houseWine + applePie
Дійсно, групуючи операції зліва направо, одержимо вираз типу
<float> + <MenuItem>,
справа наліво:
<MenuItem> + <float>,
жоден з яких не відповідає сигнатурі довизначення
<MenuItem>+<MenuItem>.
Насправді можливі всі чотири випадки:
MenuItem MenuItem
MenuItem float
float MenuItem
float float
А тому повне визначення оператора додавання складатиметься з чотирьох частин:
class MenuItem
{
private:
float price;
string name;
public:
MenuItem( float, string);
MenuItem( float, char[]);
MenuItem(const MenuItem&);
float getPrice();
float operator+(const MenuItem&);
float operator+(float);
};
float MenuItem::operator+(const MenuItem& item )
{
return( getPrice() + item.getPrice() );
}
float MenuItem::operator+( float subtotal )
{
return( GetPrice() + subtotal );
}
плюс ще одна утиліта класу
float operator+( float subtotal,const MenuItem& item )
{
return( subtotal + item.getPrice() );
}
плюс стандартна операція додавання дійсних чисел.
Другий варіант полягає у визначенні перетворення MenuItem в дійсне число
MenuItem::operator float(){return price;}
Перетворення типів обходиться меншою кількістю функцій, але вимагає більшої
кількості викликів.
Асоціативність операцій може призвести до великих втрат пам'яті. Розглянемо
приклад матриць
class Matrix
{
static const int size;
double **M;
public:
//обчислення добутку з копіюванням результату
Matrix operator*(const Matrix&);
};
реалізація множення:
const int Matrix::size=10;
Matrix Matrix::operator+(const Matrix& b)
{
Matrix res;
for(int i=0; i<size; i++)
for(int j=0; j<size; j++)
{
double s=0;
for(int k=0; k<size; k++)
s+=this->M[i][k]*b.M[k][j];
res.M[i][j]=s;
}
return res;
}
Як буде виконуватися множення?
Matrix A, B, C, D;
D = (A * B) * C;
Чи не буде великих втрат ефективності при копіюванні проміжного результату
(A*B) до тимчасового об'єкту this , що активізує друге множення.
_this = A*B;
D= _this*C;
Було б ефективніше утримати проміжний результат на місці безпоседньо до його
використання в наступному множенні. Спробуємо так
class Matrix
{
static const int size;
double **M;
public:
//обчислення добутку з копіюванням результату
Matrix& operator*(const Matrix&);
};
Matrix& Matrix::operator*(const Matrix& b)
{
Matrix *res = new Matrix;
for(int i=0; i<size; i++)
for(int j=0; j<size; j++)
{
double s=0;
for(int k=0; k<size; k++)
s+=this->M[i][k]*b.M[k][j];
res->M[i][j]=s;
}
return *res;
}
Тепер з'явилася інша проблема: ніхто більше не відповідає за знищення проміжного
результату. Раніше об'єкт res знищувався при виході із функції. Проблема ефективності
іменованого результату так і залишається проблемою. З точки зору ефективності
доцільніше використовувати функції з модифікованими параметрами, а не операції
void mult(const Matrix& a, const Matrix& b, Matrix& res);
Правда тепер стане неможливим запис матричних виразів, а також можливі інші
несподіванки, наприклад, при виклику
mult(A, B, A);
або навіть
mult(A, A, A);
Це відома проблема кратних параметрів: якщо результат формується в одному
з параметрів, то параметр може спотворитися раніше, ніж його повністю використають.
Клас Matrix вимагає також довизначення присвоєння
Matrix& operator=(const Matrix&);
яке зводиться до поелементного копіювання двох масивів, розмірності яких співпадають.
Matrix& Matrix::operator=(const Matrix& b)
{
for(int i=0; i<size; i++)
for(int j=0; j<size; j++)
M[i][j]=b.M[i][j];
return *this;
}
Ось трохи складніший варіант
class string
{
private:
char *s;
short stringLength;
public:
string();
string( char *thestring );
~string();
void displayAddress();
string& operator=( const string &fromstring );
};
Тепер розміри об'єктів у лівій і правій частині присвоєння різні
string::string()
{
stringLength = 2;
s = new char[ stringLength + 1 ];
strcpy(s,"NN");
}
string::string( char *thestring )
{
stringLength = strlen( thestring );
s = new char[ stringLength + 1 ];
strcpy( s, thestring );
}
string::~string()
{
delete [] s;
}
void string::displayAddress()
{
cout<<s<<" at address: "<<(unsigned long)s<<"\n";
}
string& string::operator=(const string &fromstring)
{
delete [] s;
stringLength = fromstring.stringLength;
s = new char[ stringLength + 1 ];
strcpy(s, fromstring.s );
return *this;
}
Ось ілюстрація того, як використовуватиметься пам’ять
int main()
{
string theSwiss("Niklaus Wirth, TFH Zurich");
string theDutchman("Edsger Wybe Dijkstra, \
University of Texas at Austin & \
Burroughs Corporation");
string anAmerican;
string aEuropean;
theSwiss.displayAddress();
theDutchman.displayAddress();
anAmerican.displayAddress();
aEuropean.displayAddress();
cout << "-----\n";
aEuropean = theSwiss;
aEuropean.displayAddress();
cout <<"-----\n";
anAmerican = aEuropean = theDutchman;
anAmerican.displayAddress();
aEuropean.displayAddress();
return 0;
}