
- •Міністерство надзвичайних ситуацій України Львівський державний університет безпеки життєдіяльності Юрій грицюк, Тарас рак
- •Навчальний посібник
- •Потреба використання об'єктно-орієнтованого програмування
- •Поняття про об’єктно-орієнтований підхід до розроблення складних програм
- •Основні компоненти об’єктно-орієнтованої мови програмування
- •Поняття про універсальну мову моделювання
- •Базові поняття класу
- •Код програми 2.1. Демонстрація механізму оголошення класу та його застосування
- •Поняття про конструктори і деструктори
- •Особливості реалізації механізму доступу до членів класу
- •Класи і структури - споріднені типи
- •Об'єднання та класи - споріднені типи
- •Поняття про вбудовані функції
- •Inline int myClass::Put() return c;
- •Особливості організації масивів об'єктів
- •Особливості використання покажчиків на об'єкти
- •Поняття про функції-"друзі" класу
- •Код програми 3.1. Демонстрація механізму використання "дружньої" функції для доступу до закритих членів класу
- •Код програми 3.2. Демонстрація механізму використання "дружньої" функції для перевірки статусу кожного об'єкта
- •Void Run(); //Таймер відліку часу
- •Void timerClass::Run()
- •Int mainO
- •Особливості механізму динамічної ініціалізації конструктора
- •Int s; public:
- •Void Run(); //Таймер відліку часу
- •Void timerClass::Run()
- •Int mainO
- •Особливості механізму присвоєння об'єктів
- •Int а, ь; public:
- •Int mainO
- •Особливості механізму передачі об'єктів функціям
- •Void Fun(myClassobj)
- •Int mainO
- •Конструктори, деструктори і передача об'єктів
- •Void Get(myClass obj)
- •Int mainO
- •Потенційні проблеми, які виникають при передачі об'єктів
- •Int *р; public:
- •Void Get(myClass &obj) // Передача об'єкта за посиланням
- •Int mainO
- •Особливості механізму повернення об'єктів функціями
- •Void Set(char*s) {strcpy(str, s);}
- •Void Show() {cout «"Рядок:" « str« endl;}
- •Int mainO
- •Int mainO
- •Механізми створення та використання конструктора копії
- •Використання конструктора копії для ініціалізації одного об'єкта іншим
- •Int mainO
- •Механізм використання конструктора копії для передачі об'єкта функції
- •Int mainO
- •Механізм використання конструктора копії при поверненні функцією об'єкта
- •Int mainO
- •3.7.4. Конструктори копії та їх альтернативи
- •Поняття про ключове слово this
- •Void Fun() {...};
- •Int mainO
- •Механізми перевизначення операторів з використанням функцій-членів класу
- •Int х, у, z; //Тривимірні координати
- •Int mainO
- •Intх,у,z; //Тривимірні координати
- •Void Show(char*s);
- •Int mainO
- •Int х, у, z; //Тривимірні координати
- •Int mainO
- •Особливості реалізації механізму перевизначення операторів
- •Механізми иеревизначення операторів з використанням функцій-не членів класу
- •Використання функцій-"друзів" класу для перевизначення бінарних операторів
- •Void Show(char*s);
- •Int mainO
- •Int mainO
- •Використання функцій-"друзів" класу для перевизначення унарних операторів
- •Int mainO
- •Особливості реалізації оператора присвоєння
- •Int mainO
- •Int mainO
- •Механізми перевизначення оператора індексації елементів масиву "[]"
- •Int mainO
- •Int aMas[size]; public:
- •Int mainO
- •Int aMas[size]; public:
- •Int mainO
- •Механізми перевизначення оператора виклику функцій "()"
- •Int mainO
- •Механізми перевизначення рядкових операторів
- •Конкатенація та присвоєння класу рядків з рядками класу
- •Void Show(char*s) {cout« s « string « endl;}
- •Конкатенація та присвоєння класу рядків з рядками, що закінчуються нульовим символом
- •Void Show(char*s) {cout« s « string « endl;}
- •Void Show(char*s) {cout« s « string « endl;}
- •Int mainO
- •Поняття про успадкування в класах
- •Int kolesa; // Кількість коліс int pasagyr; // Кількість пасажирів public:
- •Int mistkist; // Вантажомісткість у м куб. Public:
- •Int kolesa; // Кількість коліс int pasagyr; // Кількість пасажирів public:
- •Int mainO
- •Використання специфікатора доступу protected для надання членам класу статусу захищеності
- •Int mainO
- •Int mainO
- •Int d; // Захищений public:
- •Int mainO
- •Protected-членом класу derived, що робить його недоступним за його межами. */
- •Void showX() {cout« х « endl;}
- •Void showY() {cout« у « endl;}
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Успадкування віртуальних функцій
- •Void Show() {cout« Суг("Другий похідний клас.") « endl;}
- •Virtual void Show() {cout« Суг("Базовий клас.") « endl;}
- •Void Show() {cout« Суг("Перший похідний клас. ")« endl;}
- •Int mainO
- •Virtual void ShowO {cout« Суг("Базовий клас.") « endl;}
- •Void Show() {cout« Суг("Перший похідний клас. ")« endl;}
- •Int mainO
- •Void Show()
- •Void Show()
- •Int mainO
- •Void Show()
- •Void Show()
- •Int mainO
- •Void swapAb(aType &а, аТуре &b)
- •Int mainO
- •Приклад створення узагальненого класу для організації безпечного масиву
- •Int mainO
- •Використання в узагальнених класах аргументів, що не є узагальненими типами
- •Використання в шаблонних класах аргументів за замовчуванням
- •Int mainO
- •Механізм реалізації безпосередньо заданої спеціалізації класів
- •Int mainO
- •Основні особливості оброблення виняткових ситуацій
- •Системні засоби оброблення винятків
- •Xtest(1);
- •Xtest(2);
- •Перехоплення винятків класового типу
- •Використання декількох catch-наетанов
- •Варіанти оброблення винятків
- •Перехоплення всіх винятків
- •Накладання обмежень на тип винятків, які генеруються функціями
- •Int mainO
- •Xhandler(o); // Спробуйте також передати функції XhandlerO аргументи 1 і 2.
- •Void Xhandler(int test) throw 0
- •Повторне генерування винятку
- •Int mainO
- •Int mainO
- •Механізми перевизначення операторів new і delete
- •Void *р;
- •Void *p;
- •Int mainO
- •Класи потоків
- •Особливості механізмів перевизначення операторів введення-виведення даних
- •Створення перевюначених операторів виведення даних
- •Int mainO
- •Використання функцій-"друзів" класу для перевюначення операторів виведення даних
- •Int х, у, z; //Тривимірні координати (тепер це private-члени) public:
- •Int mainO
- •Створення перевюначених операторів введення даних
- •Istream &operator»(istream &stream, kooClass &obj)
- •Cout«"Введіть координати X, у і z:
- •Stream » obj.X » obj.Y » obj.Z;
- •Istream &operator»(istream &stream, objectType &obj)
- •// Код операторної функції введення даних
- •Class kooClass {// Оголошення класового типу int х, у, z; // Тривимірні координати
- •Istream &operator»(istream &stream, kooClass &obj)
- •Int mainO
- •Void unsetf(fmtflags flags),
- •Void showflags(ios::fmtflags f); // Відображення поточного стану опцій
- •Int mainO
- •Ios::fmtflags f; // Оголошення параметру для поточного стану опцій
- •Int mainO
- •Створення власних маніиуляторних функцій
- •Організація файлового введення-виведення даних
- •Відкриття та закриття файлу
- •Зчитування та запис текстових файлів
- •Ifstream in(argv[1], ios::in | ios::binary); if(!in){
- •In.CloseO; getchO; return 0;
- •Int mainO
- •Зчитування та записування у файл блоків даних
- •Int mainO
- •Ifstream inftest", ios::in | ios::binary); if(!in){
- •Використання функції eof() для виявлення кінця файлу
- •If(!in.Eof()) cout« ch;
- •In.CloseO; getchO; return 0;
- •Int main(int arge, char *argv[])
- •Ifstream f1(argv[1], ios::in | ios::binary); if(!f1) {
- •Ifstream f2(argv[2], ios::in | ios::binary);
- •Int main(int arge, char *argv[])
- •Ifstream in(argv[1], ios::in | ios::binary); if(!in){
- •In.Seekg(atoi(argv[2]), ios::beg); while(in.Get(ch)) cout« ch; getchO; return 0;
- •Int х, у, z; // Тривимірні координати; вони тепер закриті public:
- •Int mainO
- •Virtual void Fun() {}; // Робимо клас Base поліморфним
- •Int mainO
- •Virtual void FunO {cout«"у класі Base"« endl;}
- •Int mainO
- •Virtual void FunO {}
- •Void derivedOnlyO {cout«"Це об'єкт класу Derived"« endl;}
- •Int mainO
- •Void Fun(const int *р)
- •Int mainO
- •Namespace ns { int d;
- •Застосування настанови using
- •Int Comp(const void *а, const void *b);
- •Int mainO
- •Int Comp(const void *a, const void *b)
- •Int mainO
- •Int Fun10 const; // const-функція-член
- •Int PutO const; return c; // Все гаразд
- •Int mainO
- •0Bj.Set(1900); cout « Obj.PutO;
- •Void setJ(int х) const // Наступна функція не відкомпілюється.
- •Int а; public:
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int myClass::*dp; // Покажчик на int-члена класу void (myClass::*fp)(int X); // Покажчик на функцію-члена
- •Int mainO
- •Механізми роботи з векторами
- •Int mainO
- •1 234 5 678 9 10 11 12 13141516 17 1819 Вміст подвоєно:
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Int mainO
- •Символи н
- •Символів представляють голосні звуки.
- •Int mainO
- •Int mainO
- •Int mainO
- •Void getaLine(string& inStr); // Отримання рядка тексту char getaCharO; //Отримання символу
- •Int aptNumber; // Номер кімнати мешканця
- •Void DisplayO; // Виведення переліку мешканців
- •Int aptNo; float rent[12]; public:
- •Void setRent(int, float); // Запис плати за місяць
- •Void insertRent(int, int, float); void DisplayO;
- •Int month, day; string category, payee; float amount; expense() {}
- •Int mainO
- •Void rentRecord::insertRent(int aptNo, int month, float amount)
- •SetPtrsRr.Insert(ptrRow); // Занести рядок вектор
- •If( setPtrsRr.Empty())
- •Else // Інакше
- •Int month, day; string category, payee; float amount;
- •«" 'Є' для запису витрат:";
- •Навчальний посібник
Virtual void FunO {}
};
class Derived: public Base { public:
Void derivedOnlyO {cout«"Це об'єкт класу Derived"« endl;}
};
Int mainO
{
Base *bp, ObjB;
Derived *dp, ObjD;
// Використання оператора typeid
bp = &ObjB; // Присвоєння покажчику адреси об'єкта базового класу
if(typeid(*bp) == typeid(Derived)) { dp = (Derived *) bp; dp->derivedOnlyO;
}
else
cout«"Операції приведення об'єкта типу Base до"
«" типу Derived не здійснено"« endl;
bp = &ObjD; // Присвоєння покажчику адреси об'єкта похідного класу if(typeid(*bp) == typeid(Derived)) { dp = (Derived *) bp; dp->derivedOnlyO;
}
else
cout«"Помилка, приведення типу має"«"бути реалізовано!"« endl;
// Використання оператора dynamic_cast
bp = &ObjB; // Присвоєння покажчику адреси об'єкта базового класу
dp = dynamic_cast<Derived *> (bp);
if(dp) dp->derivedOnlyO; else
cout«"Операції приведення об'єкта типу Base до"
«" типу Derived не здійснено"« endl; bp = &ObjD; // Присвоєння покажчику адреси об'єкта похідного класу dp = dynamic_cast<Derived *> (bp);
if(dp) dp->derivedOnlyO; else
cout«"Помилка, приведення типу має"«"бути реалізовано!"« endl; getchO; return 0;
}
Як бачите, використання оператора dynamic_cast спрощує логіку, необхідну для перетворення покажчика на базовий клас у покажчик на похідний клас. Ось як виглядають результати виконання цієї програми:
Операції приведення об'єкта типу Base до типу Derived не здійснено.
Це об'єкт класу Derived. Операції приведення об'єкта типу Base до типу Derived не здійснено. Це об'єкт класу Derived.
Нео! хідноапам ятати! Оператор dynamic_cast можна також використовувати стосовно шаблонних класів.
10.2.2. Оператор перевюначення модифікаторів const_cast
Оператор const_cast використовують для безпосереднього перевизначення модифікаторів const і/або volatile. Новий тип повинен збігатися з початковим, за винятком його атрибутів const або volatile. Найчастіше оператор const_cast використовують для видалення ознаки постійності (атрибуту const). Його загальний формат має такий вигляд: const_cast<fype> (ехрі)
У цьому записі елемент type задає новий тип операції приведення, а елемент ехрг означає вираз, який приводиться до нового типу.
Використання оператора const_cast продемонстровано в такому коді програми.
Код програми 10.9. Демонстрація механізму використання оператора const_cast #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
Void Fun(const int *р)
{
int *v;
v = const_cast<int *> (p); // Перевизначення const-атрибуту.
*v = 100; // Тепер об'єкт можна модифікувати
}
Int mainO
{
int x = 99;
cout«"Значення x до виклику функції Fun():"« x « endl;
Fun(&x);
cout«"Значення x після виклику функції Fun():"« x « endl; getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані такі результати: Значення х до виклику функції Fun(): 99 Значення х після виклику функції FunQ: 100
Як бачите, змінна х була модифікована функцією Fun(), хоча параметр, який вона приймає, задається як const-покажчик.
Необхідно наголосити на тому, що використання оператора const_cast для видалення const-атрибуту є потенційно небезпечним засобом. Тому поводьтеся з ним дуже обережно.
Вартоа ' пати! Видаляти const-атрибут здатний тільки оператор const_cast. Іншими словами, ні dynamic_cast, ні static_cast, ні reinterpret_cast не
можна використовувати для зміни const-атрибуту об'єкта.
Оператор неполіморфного приведення типів static_cast
Його можна використовувати для будь-якого стандартного перетворення. При цьому під час роботи коду програми ніяких перевірок на допустимість не здійснюється. Оператор static_cast має такий загальний формат запису: static_cast<fype> (ехрі)
У цьому записі елемент type задає новий тип операції приведення, а елемент ехрг означає вираз, який приводиться до цього нового типу.
Оператор static_cast, по суті, є замінником оригінального оператора приведення типів. Він тільки здійснює неполіморфне перетворення. Наприклад, у процесі виконання наведеної нижче програми змінна типу float приводиться до типу int.
Код програми 10.10. Демонстрація механізму використання оператора static_cast #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
int mainO
{
int с;
float f;
f = 199.22F;
c = stat ic_cast < і nt> (f);
cout« c;
getchO; return 0;
}
Оператор перетворення типу reinterpret_cast
Оператор reinterpret_cast перетворить один тип у принципово інший. Наприклад, його можна використовувати для перетворення покажчика в ціле значення і ціле значення у покажчик. Його також можна використовувати для приведення спадково несумісних типів покажчиків. Цей оператор має такий загальний формат запису:
reinterpret_cast<fype> (ехрі)
У цьому записі елемент type задає новий тип операції приведення, а елемент ехрг означає вираз, який приводиться до цього нового типу. Використання оператора reinterpret_cast продемонстровано в такому коді програми.
Код програми 10.11. Демонстрація механізму використання оператора reinterpret_cast
#include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
int mainO
{
int с;
char*p = "Це короткий рядок.";
с = reinterpret_cast<int> (р); // Приводимо покажчик до типу int.
cout« с;
getchO; return 0;
}
У цьому коді програми оператор reinterpret_cast перетворить покажчик р у ці- лочисельне значення. Внаслідок такого перетворення відбувається фундаментальна зміну типу.
Порівняння звичайної операції приведення типів з новими чотирма cast-операторами
Можливо, комусь з Вас могло б видатися, що описані вище чотири cast-one - ратори повністю замінюють традиційну операцію приведення типів. На запитання: "Чи варто завжди замість звичайної операції приведення типів використовувати новіші засоби?". Відповімо - загального правила для всіх програмістів не існує. Оскільки нові оператори були створені для підвищення безпеки достатньо ризикованої операції приведення одного типу даних до іншого, багато С++-програмістів переконані у тому, що їх необхідно використовувати виключно з цією метою. І тут важко що-небудь заперечити. Інші ж програмісти вважають, що, оскільки традиційна операція приведення типів надійно слугувала їм протягом багатьох років, то від неї не варто так легко відмовлятися. Наприклад, для виконання простих і відносно безпечних операцій приведення типів (як ті, що потрібні під час виклику функцій введення-виведення read() і writeO, описаних у попередньому розділі) "старий добрий" засіб цілком прийнятний.
Існує ще одна точка зору, з якою важко не погодитися: у процесі виконання операцій приведення поліморфних типів безумовно варто використовувати оператор dynamic_cast.
Розділ 11. ПОНЯТТЯ ПРО ПРОСТОРИ ІМЕН ТА ІНШІ ЕФЕКТИВНІ ПРОГРАМНІ ЗАСОБИ
У цьому розділі буде описано основні поняття про простори імен і такі ефективні програмні засоби, як explicit-конструктори, покажчики на функції, static-чле- ни, const-функції-члени, альтернативний синтаксис ініціалізації членів-даних класу, оператори вказання на члени класу, ключове слово asm, специфікація компонування і функції перетворення.
Особливості організації простору імен
Простори імен стисло розглянуто у розд. 2 [9]. Там було сказано, що вони дають змогу локалізувати імена ідентифікаторів, щоб уникнути конфліктних ситуацій з ними. У С++-середовищі програмування використовується величезна кількість імен змінних, функцій та імен класів. До введення поняття простору імен всі ці імена конкурували між собою за пам'ять в глобальному просторі імен, що і було причиною виникнення багатьох конфліктів. Наприклад, якби у Вашій програмі було визначено функцію toupper(), то вона могла б (залежно від переліку параметрів) перевизначити стандартну бібліотечну функцію toupper(), оскільки обоє імен мали б зберігатися в глобальному просторі імен. Конфлікти з іменами виникали раніше також під час використання однією програмою декількох бібліотек сторонніх виробників. У цьому випадку ім'я, що є визначеним у одній бібліотеці, конфліктувало з таким самим іменем з іншої бібліотеки. Така ситуація особливо неприйнятна під час використання однойменних класів. Наприклад, якщо у Вашій програмі визначено клас VideoMode, і в бібліотеці, що використовуються Вашою програмою, визначено клас з таким самим іменем, то конфлікту не уникнути.
Простір імен визначає деяку декларативну область.
Для вирішення описаної проблеми було створено ключове слово namespace. Оскільки воно локалізує видимість оголошених у ньому імен, то це означає, що простір імен дає змогу використовувати одне і те саме ім'я в різних контекстах, не викликаючи при цьому конфлікту імен. Можливо, найбільше від нововведення "поталанило" С++-бібліотеці стандартних функцій. До появи ключового слова namespace (яке було, звичайно ж, єдиним) всю С++-бібліотеку було визначено в глобальному просторі імен. З настанням namespace-"epH" С++-бібліотека визначається у власному просторі імен, названий std, який значно знизив ймовірність виникнення конфліктів імен. У своїй програмі програміст повинен створювати власні простори імен, щоб локалізувати видимість тих імен, які, на його думку, можуть стати причиною конфлікту. Це особливо важливо, якщо Ви займаєтеся створенням бібліотек класів або функцій.
Поняття про простори імен
Ключове слово namespace дає змогу розділити глобальний простір імен шляхом створення певної декларативної області. По суті, простір імен визначає область видимості. Загальний формат задавання простору імен є таким: namespace пате {
II Оголошення
}
Все, що визначено у межах настанови namespace, знаходиться в області видимості цього простору імен.
У наведеному нижче коді програми показано приклад використання namespa- се-настанови. Вона локалізує імена, що використовуються для виконання розрахунку у зворотному порядку. У створеному тут просторі імен визначається клас counter, який реалізує лічильник, і змінні upperbound і lowerbound, що містять значення верхньої і нижньої меж, які використовуються для всіх лічильників.
// Приклад використання namespace-настанови namespace CounterNameSpace {
int upperbound; int lowerbound;
class counter {
int count; public:
counter(int n) {
if(n <= upperbound) count = n; else count = upperbound;
}
void Reset(int n) {
if(n <= upperbound) count = n;
}
int RunO {
if(count > lowerbound) return count-; else return lowerbound;
}
};
}
У цьому коді програми змінні upperbound і lowerbound, а також клас counter є частиною області видимості, визначеної простором імен CounterNameSpace.
У будь-якому просторі імен до ідентифікаторів, які у ньому оголошені, можна звертатися безпосередньо, тобто без вказання цього простору імен. Наприклад, у функції Run(), яка знаходиться у просторі імен CounterNameSpace, можна безпосередньо звертатися до змінної lowerbound: if(count > lowerbound) return count—;
Але, оскільки настанова namespace визначає область видимості, то під час звернення до об'єктів, оголошених у просторі імен, ззовні цього простору необхідно використовувати оператор дозволу області видимості. Наприклад, щоб присвоїти значення 10-ій змінній upperbound з коду програми, який є зовнішнім стосовно простору імен CounterNameSpace, потрібно використовувати таку настанову: CounterNameSpace::upperbound = 10;
Щоб оголосити об'єкт типу counter поза простором імен CounterNameSpace, потрібно використати настанову, подібну до такої:
CounterNameSpace::counter obj;
У загальному випадку, щоб отримати доступ до деякого члена простору імен ззовні цього простору, необхідно, щоби імені цього члена передувало ім'я простору і розділити ці імена оператором дозволу області видимості.
Розглянемо невелику програму, у якій продемонстровано механізм використання простору імен CounterNameSpace.
Код програми 11.1. Демонстрація механізму використання простору імен
CounterNameSpace для розрахунку у зворотному порядку
#include <vcl>
#include <iostream> // Для потокового введення-виведення
#include <conio> // Для консольного режиму роботи
using namespace std; // Використання стандартного простору імен
// Приклад використання namespace-настанови namespace CounterNameSpace {
int upperbound; int lowerbound;
class counter { int count; public:
counter(int n) {
if(n <= upperbound) count = n; else count = upperbound;
}
void Reset(int n) {
if(n <= upperbound) count = n;
}
int RunO {
if(count > lowerbound) return count-; else return lowerbound;
}
};
}
int mainO
{
CounterNameSpace::upperbound = 100;
CounterNameSpace::lowerbound = 0;
CounterNameSpace::counter ObjA(10);
CounterNameSpace::counter ObjB(2Q);
int с;
cout«"Розрахунок у зворотному порядку для об'єкта ObjA" « endl; do {
с = ObjA.RunO; cout« c «"
} while(c > CounterNameSpace::lowerbound); cout« endl;
cout«"Розрахунок у зворотному порядку для об'єкта ObjB" « endl; do {
с = ObjB.RunO; cout« c «"";
} while(c > CounterNameSpace::lowerbound); cout« endl;
ObjB.Reset(IOO);
CounterNameSpace::lowerbound = 80;
cout«"Розрахунок у зворотному порядку для об'єкта ObjB" « endl; do {
с = ObjB.RunO; cout« c «"";
} while(c > CounterNameSpace::lowerbound); cout« endl;
getchO; return 0;
}
Зверніть увагу на те, що при створенні об'єкта класу counter і при зверненні до змінних upperbound і lowerbound використовують ім'я простору імен CounterNameSpace. Але після створення об'єкта типу counter вже немає потреби у повній кваліфікації його самого або його членів. Оскільки простір імен однозначно визначено, то функцію Run() об'єкта ObjA можна викликати безпосередньо, тобто без вказання (як префікс) простору імен (ObjA.Run()).
Програма може містити декілька оголошень просторів імен з однаковими іменами. Це означає, що простір імен можна поділити на декілька файлів або на декілька частин у рамках одного файлу. Зробити це можна так: namespace NS { int с;
}
II....