- •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.6 Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу
Поточний об'єкт this, указники на члени класу і класні функції, порівняння з указниками на (позакласні) функції, указники на статичні члени класу
Тепер подивимося, як методи дістаються до своїх атрибутів. Наприклад, визначимо
два вікна
const Screen w(4,3,"aaaabbbbcccc");
Screen u(w);
кожен з них змінить значення свого курсору, так що присвоєння
_cursor = _width*i + j;
застосовується до свого атрибуту:
w._cursor == 11
а
u._cursor == 14.
Як бачимо, метод, активізований об'єктом, повинен знати, який саме об'єкт,
його активізував. Для цього існує указник поточного об'єкту this , його тип
Screen* . Отже повний текст будь-якого методу, наприклад, методу move мав би
наступний вигляд
void Screen::move(int i, int j) const
{
if ((i>=this->_height) || (j>=this->_width))
this->_cursor=0;
else
this->_cursor = this->_width*i + j;
};
або для копіювального конструктора
Screen::Screen(const Screen& v)
{
this->_filler = v._filler;
this->_height = v._height;
this->_width = v._width;
this->_wContent = new char [_height*_width+1];
strcpy(this->_wContent,v._wContent);
this->_cursor=v._cursor;
}
Ясно, що в усіх цих випадках діє правило замовчування: “безпритульні” атрибути
і методи приписуються поточному об'єкту. Але легко уявити собі ситуацію, коли
явне посилання на поточний об'єкт стає необхідним. Як приклад розглянемо копіювання
параметру до поточного об'єкту
void Screen::copy(const Screen& v)
{
if (this != &v)
{
delete []_wContent;
_height = v._height;
_width = v._width;
_wContent = new char [_height*_width+1];
strcpy(_wContent,v._wContent);
_cursor=v._cursor;
}
};
Копіюванню передує перевірка на збіг об'єкту, що копіюється, з цільовим об'єктом.
Якщо такої перевірки не зробити, то доведеться виконувати зайві і , власне,
не цілком коректні дії.
Інше важливе застосування указника на поточний об'єкт виникає при необхідності
повернення поточного об'єкту результатом функції. Найчастіше ця ситуація виникає
при довизначенні операцій. Вона буде розглянута в наступному розділі. Ось ще
один типовий приклад. Розглянемо таку послідовність дій
Screen myScreen(3,3,””);
myScreen.clear();
myScreen.move(1,1);
myScreen.set(’*’);
myScreen.move(2,2);
myScreen.set(’*’);
myScreen.move(3,3);
myScreen.set(’*’);
myScreen.show();
Існує простий спосіб, як записати цю послідовність інструкцій у вигляді одного
виразу
myScreen.clear().move(1,1).set(’*’).
move(2,2).set(’*’).
move(3,3).set(’*’).show();
Для цього функції повинні повертати значенням свій поточний об'єкт
Screen& Screen::home()
{
_cursor=0;
return *this;
};
Screen& Screen::move(int i, int j)
{
if ((i>=_height) || (j>=_width))
_cursor=0;
else
_cursor = _width*i + j;
return *this;
};
Screen& Screen::move()
{
if ((++_cursor)>=_width*_height) _cursor=0;
return *this;
};
Screen& Screen::set(char a)
{
*(_wContent + _cursor)=a;
return *this;
}
Screen& Screen::clear()
{
for (int i=0; i<_height*_width; i++)
*(_wContent+i)= _filler;
_cursor=0;
return *this;
}
та відповідно модифікувавши інтерфейс
class Screen
{
public:
Screen(int, int, char*);
Screen(const Screen&);
~Screen();
void home() const;
void move(int, int) const;
void move() const;
char get() const;
void show() const;
void copy(const Screen&);
Screen& home();
Screen& move(int, int);
Screen& move();
Screen& set(char);
Screen& clear();
private:
static const int maxHeight;
static const int maxWidth;
int _height;
int _width;
char *_wContent;
mutable int _cursor;
char _filler;
};
Зверніть увагу на те, що ново визначені функції не константні. В принципі
їх можна визначати константними, якщо добавити кваліфікатор const двічі
const Screen& home() const ;
інакше значення об'єкту можна було б змінити, помістивши виклик в позицію,
яка вимагає лівостороннього значення.
Тепер розглянемо проблеми непрямого доступу до членів класу за допомогою указників
у об'єктах.
Ось простий клас калькулятора з суматором і двома регістрами
class Calc
{
friend void utilityCalc();
unsigned int a;
unsigned int x;
unsigned int y;
public:
unsigned int add();
unsigned int mult();
unsigned int div();
unsigned int subt();
}
Арифметичні операції виконуються на суматорі, наприклад,
unsigned int Calc::add() { return a=a+x;}
Якщо ми візьмемо об'єкт c класу Calc , то атрибут c.a матиме тип unsigned
int . Це значить, що будь-якій змінній i типу unsigned int можна присвоїти
значення c.a . Спроба присвоїти змінній i просто значення a ясна річ не коректна.
Так само як некоректна спроба помістити на a указник типу unsigned int* . Дійсно
для прямого чи непрямого звертання до атрибуту необхідно знати об'єкт.
void utilityCalc()
{
Calc c;
unsigned int i, *ip;
i=c.a; //OK
i=a; //Error
ip=&a; //Error
}
Але все ж таки можна домовитися позначати певний атрибут в об'єкті за допомогою
указника, надавши указнику іншого типу, наприклад, anyReg
unsigned int Calc::*anyReg;
визначено як указник атрибуту типу unsigned int в об'єкті типу Calc .
Відповідно надамо йому значення
anyReg = &Calc::a;
сказавши, що указник показуватиме на атрибут у об'єктах Calc.
Також можна скористатися визначенням нового типу
typedef unsigned int Calc::*Register;
Register r = &Calc::a;
Щоб добратися до самого атрибуту об'єкт все ж таки потрібен
Calc c;
c.*anyReg = 1;
присвоєння відбудеться атрибуту, на який вказує anyReg , тобто c.a .
Ось трохи складніший приклад
void CycleShiftLeft
(unsigned int Calc::*regPtr, Calc c[], int n)
{
for (int i=0; i<n; i++)
{
unsigned int mask = 0x80000000;
mask = (c[i].*regPtr & mask) << 31;
(c[i].*regPtr<<=1)|=mask;
}
}
В масиві c відбувається циклічний зсув атрибуту, позначеного указником regPtr
.
Схожі проблеми виникають при спробі непрямого доступу до класних функцій.
Те що об'єкти класу розділяють функції не спрощує проблеми, кожна функція знає
свій об'єкт, який її активізував. Для цього в ній присутній указник this .
Ось, наприклад, указник op на функцію без параметра з класу Calc
unsigned int (Calc::*op) ();
Доповнимо клас Calc функцією exe , що виконує довільну операцію калькулятора
class Calc
{
friend void utilityCalc();
unsigned int a;
unsigned int x;
unsigned int y;
public:
unsigned int add();
unsigned int mult();
unsigned int div();
unsigned int subt();
unsigned int exe(unsigned int (Calc::*) ());
}
її реалізація
unsigned int Calc::exe(unsigned int (Calc::*op) ())
{
return (this->*op)();
}
А ось приклад виклику
void utilityCalc()
{
Calc b;
b.x=10; b.a=1;
cout<<b.exe(&Calc::add)<<endl;
}
Ще один приклад показує особливості посилань на константні і неконстантні
функції
typedef void (Screen::*Action) () const;
void doAction1(Screen& s, Action act, int n)
{
for (int i=0;i<n;i++)
(s.*act)();
}
typedef Screen& (Screen::*AnotherAction) ();
void doAction2(Screen& s, AnotherAction act, int n)
{
for (int i=0;i<n;i++)
(s.*act)();
}
Тут визначено дві позакласні функції для повторення довільної дії з класу
Screen. А ось приклад їх виклику
Screen v(2,4,"11223344");
doAction1(v, Screen::move, 7);
doAction1(v, Screen::back, 5);
doAction2(v, Screen::move, 7);
doAction2(v, Screen::back, 2);
Пересвідчіться, що doAction1 викликає функцію з сигнатурою
void move() const;
а doAction1
Screen& move();
Також можна влаштувати масив з указників на класні функції
Action menu[] =
{
Screen::home,
Screen::move,
Screen::back,
Screen::show
};
Указники на статичні атрибути і статичні методи нічим не відрізняються від
звичайних указників даних і функцій, оскільки статичні атрибути знаходяться
поза об'єктами, а статичні функції не знають поточного об'єкту.
class Account
{
friend class Bank;
static double interestRate;
public:
static void raiseInterest(double);
static double getInterest();
};
class Bank
{
double * RatePtr;
void (* raisePtr)(double);
double (* getPtr)();
public:
Bank();
};
Bank::Bank()
{
RatePtr = &Account::interestRate;
raisePtr = &Account::raiseInterest;
getPtr = &Account::getInterest;
}
