
- •Міністерство надзвичайних ситуацій України Львівський державний університет безпеки життєдіяльності Юрій грицюк, Тарас рак
- •Навчальний посібник
- •Потреба використання об'єктно-орієнтованого програмування
- •Поняття про об’єктно-орієнтований підхід до розроблення складних програм
- •Основні компоненти об’єктно-орієнтованої мови програмування
- •Поняття про універсальну мову моделювання
- •Базові поняття класу
- •Код програми 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;
- •«" 'Є' для запису витрат:";
- •Навчальний посібник
Int mainO
{
map<string, string> dictionary;
dictionary.insert(pair<string, string>("fliM", "Місце мешкання.")); dictionary.insert(pair<string, string>("iuiaBiaTypa", "Пристрій введення даних.")); dictionary.insert(pair<string, з(гіпд>("програмування", "Процес розроблення програми.")); dictionary.insert(pair<string, string>("STL", "Standard Template Library."));
string s;
cout«"Введіть слово:"; сіп » s;
map<string, string>::iteratorp;
p = dictionary .find(s); if(p != dictionary.endO)
cout«"Визначення:"« p->second « endl;
else
cout«"Такого слова у словнику немає." « endl; getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані такі результати:
Введіть слово: дім Визначення: Місце мешкання.
Введіть слово: хата Такого слова у словнику немає.
Введіть слово: програмування Визначення: Процес розроблення програми.
Введіть слово: STL
Визначення: Standard Template Library.
Розділ 13. ОСОБЛИВОСТІ РОБОТИ ПРЕПРОЦЕСОРА C++
Цей розділ навчального посібника присвячено вивченню особливостей роботи препроцесора C++. Препроцесор мови програмування C++ - це частина компілятора, яка піддає Вашу програму різним текстовим перетворенням до реальної трансляції початкового коду програми в об'єктний. Програміст може давати преп- роцесору команди, що задаються директивами препроцесора (preprocessor directives), які, не будучи формальною частиною мови програмування C++, здатні розширити область дії його середовища програмування.
Директиви препроцесора C++
#define |
#error |
#include |
#if |
#else |
#el if |
#endif |
#ifdef |
#ifndef |
#undef |
#line |
#pragma |
Як бачите, всі директиви препроцесора починаються з символу "#". Тепер розглянемо кожну з них окремо.
Нео! хгдноапам' ятати! Препроцесор мови програмування C++ є прямим нащадком препроцесора мови С, і деякі його засоби виявилися надлишковими після введення у мові програмування C++ нових елементів. Проте він, як і раніше, є важливою частиною C++-середовища програмування.
Поняття про директиви препроцесора C++
Директива #define. Цю директиву використовують для визначення ідентифікатора та імені символьної послідовності, яка буде підставлена замість ідентифікатора скрізь, де він траплятиметься у початковому коді програми. Цей ідентифікатор називають макроіменем, а процес заміни - макропідстановкою (реалізацією макророзширення). Загальний формат використання цієї директиви має такий вигляд:
#define макроім'я поспідовність_символів
Зверніть увагу на те, що у цьому записі немає крапки з комою. Задана поспі- довність_символів завершується тільки символом кінця рядка. Між елементами ім'я_макроса і послідовність_символів може бути будь-яка кількість пропусків.
Директива #define визначає ім'я макросу.
Отже, після внесення цієї директиви кожне входження текстового фрагмента, що є визначеним як макроім'я, замінюють заданим елементом послідовність_символів. Наприклад, якщо виникає бажання використовувати слово UP як значення 1 і слово DOWN як значення 0, оголосіть такі директиви #define:
#define UP 1 #define DOWN 0
Дані директиви змусять компілятор підставляти 1 або 0 кожного разу, коли у файлі початкового коду програми трапиться слово UP або DOWN відповідно. Наприклад, у процесі виконання такої настанови cout« UP «"" « DOWN «"" « UP + UP; на екран буде виведено таке:
102
Після визначення імені макросу його можна використовувати як частину визначення інших макроімен. Наприклад, такий програмний код визначає імена ONE, TWO і THREE і відповідні їм значення:
#define ONE 1 #define TWO ONE+ONE #define THREE ONE+TWO
Важливо розуміти, що макропідстановка - це просто заміна ідентифікатора відповідним рядком. Отже, якщо виникає потреба визначити стандартне повідомлення, то використовують програмний код, подібний до цього:
#define GETFILE "Введіть ім'я файлу"
.
Препроцесор замінить рядком "Введіть ім'я файлу" кожне входження ідентифікатора GETFILE. Для компілятора ця cout-настанова cout« GETFILE; насправді виглядає так:
cout«"Введіть ім'я файлу";
Ніякої текстової заміни не відбудеться, якщо ідентифікатор знаходиться в рядку, поміщеному в лапки. Наприклад, у процесі виконання такого коду програми
#define GETFILE "Введіть ім'я файлу"
.
cout«"GETFILE - це макроім'я"« endl;
на екрані буде відображена ця інформація GETFILE - це макроім'я а не така:
Введіть ім'я файлу - це макроім'я
Якщо текстова послідовність не поміщається в рядку, то її можна продовжити в наступному, поставивши зворотну косу риску у кінці рядка, як це показано у такому прикладі:
#define LONG_STRING "Це дуже довга послідовність, \ яка використовується як приклад."
Серед С++-програмістів прийнято використовувати для макроімен прописні букви. Ця домовленість дає змогу з першого погляду зрозуміти, що тут використовується макропідстановка. Окрім цього, найкраще помістити всі директиви #de- fine у початок файлу або включити в окремий файл, щоб не шукати їх потім у всій програмі.
Макропідстановки часто використовують для визначення "магічних чисел" програми. Наприклад, у Вас є програма, яка визначає певний масив, і ряд функцій, які отримують доступ до нього. Замість "жорсткого" кодування розміру масиву за допомогою константи краще визначити ім'я, яке б представляло розмір, а потім використовувати це ім'я скрізь, де повинен знаходитися розмір масиву. Тоді, якщо цей розмір доведеться змінити, Вам достатньо буде внести тільки одну зміну, а потім перекомпілювати програму. Розглянемо такий приклад:
#define Max_size 100
II...
float balance[Max_size]; double index[Max_size]; int num_emp[Max_sizej;
Нео! хідноапам'ятати'.аУ мові програмування C++ передбачено ще один спосіб визначення констант, який полягає у використанні специфікатора const. Проте багато програмістів "прийшли" у мову програмування C++ з С- середовища, де для цих потреб зазвичай використовувалася директива #define. Тому Вам ще часто доведеться з нею мати справу у С++-коді програми.
Макровизначення, що діють як функції. Директива #define має ще одне призначення: макроім'я може використовуватися з аргументами. При кожному входженні макроімені пов'язані з ним аргументи замінюються реальними аргументами, вказаними у коді програми. Такі макровизначення діють подібно до функцій. Розглянемо такий приклад.
Код програми 13.1. Демонстрація механізму використання "функціональних" макровизначень #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define MIN(a, b) (((a)<(b))? a: b) int mainO {
int x = 10; у = 20;
cout«"Мінімум дорівнює:"« MIN(x, у);
getchO; return 0;
}
У процесі компілювання цієї програми вираз, визначений ідентифікатором MIN(a, b), буде замінено, але х і у розглядатимуться як операнди. Це означає, що з out-настанова після компілювання виглядатиме так: cout«"Мінімум дорівнює: "«(((х)<(у))?х:у);
По суті, таке макровизначення є способом визначити функцію, яка замість виклику дає змогу розкрити свій код у рядку.
Макровизначення, що діють як функції, - це макровизначення, які приймають аргументи. Круглі дужки, що здаються надлишковими, в які поміщено макровизначення MIN, необхідні для того, щоби гарантувати правильне сприйняття компілятором замінюваного виразу. Насправді додаткові круглі дужки повинні застосовуватися практично до всіх макровизначень, що діють подібно до цієї функцій. Потрібно завжди дуже уважно ставитися до визначення таких макросів; інакше можливе отримання несподіваних результатів. Розглянемо, наприклад, цю навчальну програму, яка використовує макрос для визначення парності значення.
Код програми 13.2. Демонстрація неправильної роботи коду програми #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define EVEN (а) а%2==0? 1 :0 int mainO {
if(EVEN(9 +1)) cout «"парне число";
else cout«"непарне число";
getchO; return 0;
}
Ця програма не працюватиме коректно, оскільки не забезпечена правильна підстановка значень. У процесі компілювання вираз EVEN(9 + 1) буде замінений таким чином:
9+1 %2==0 ? 1 :0
Нагадаю, що оператор ділення за модулем "%" має вищий пріоритет, ніж оператор додавання "+". Це означає, що спочатку виконається операція ділення за модулем (%) для числа 1, а потім її результат буде складний з числом 9, що (звичайно ж) не дорівнює 0. Щоб виправити помилку, достатньо помістити у круглі дужки аргумент, а в макровизначенні EVEN, як це показано в наступній (виправленій) версії тієї ж самої програми.
Код програми 13.3. Демонстрація коректної роботи коду програми #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define EVEN (а) (а)%2==0? 1 :0 int mainO {
if(EVEN(9 +1)) cout «"парне число";
else cout«"непарне число";
getchO; return 0;
}
Тепер сума 9+1 обчислюється до виконання операції ділення за модулем. У загальному випадку краще завжди брати параметри макровизначення у круглі дужки, щоб уникнути непередбачених результатів, подібних описаному вище.
Використання макровизначень замість справжніх функцій має одну істотну перевагу: оскільки програмний код макровизначення розширюється в рядку, і немає ніяких витрат системних ресурсів на виклик функції, то швидкість роботи Вашої програми буде вищою порівняно із застосуванням звичайної функції. Але підвищення швидкості є платою за збільшення розміру програми (через дублювання коду функції).
Нео! хідноа пам'ятати!а Незважаючи на те, що макровизначення все ще трапляється у С++-коді програми, макроси, що діють подібно до функцій, можна замінити специфікатором ІПІІПЄ, який справляється з тією ж функцією краще і безпечніше. (Пригадайте: специфікатор ІПІІПЄ забезпечує замість виклику функції розширення її тіла в рядку). Окрім цього, ІПІІпе-функцй не вимагають додаткових круглих дужок, без яких не можуть обійтися макровизначення. Проте макроси, що діють подібно до функцій, все ще залишаються частиною С++-програм, оскільки багато хто з C/C++-програмістів продовжує використовувати їх за звичкою.
Директива #еггог. Ця директива дає вказівку компіляторові зупинити компілювання. Вона використовується здебільшого для відлагодження. Загальний формат її запису є таким:
#еггог повідомлення
Зверніть увагу на те, що елемент повідомлення не поміщений у подвійні лапки. При потраплянні на директиву #еггог відображається задане повідомлення та інша інформація (вона залежить від конкретної реалізації робочого середовища), після чого компілювання припиняється. Щоб дізнатися, яку інформацію відображає у цьому випадку компілятор, достатньо провести експеримент.
Директива #ЄГГОГ відображає повідомлення про помилку.
Директива препроцесора #include. Ця директива зобов'язує компілятор включити або стандартний заголовок, або інший початковий файл, ім'я якого вказане у директиві #include. Ім'я стандартних заголовків береться у кутові дужки, як це показано у прикладах, наведених у цьому навчальному посібнику. Наприклад, ця директива
#include <vector> // Для роботи з контейнерним класом "Вектор"
містить стандартний заголовок для векторів.
Директива #include містить заголовок або інший початковий файл.
Під час включення іншого початкового файлу його ім'я може бути вказане у подвійних лапках або кутових дужках. Наприклад, наступні дві директиви зобов'язують компілятор C++ прочитати і скомпілювати файл з іменем sample.h:
#include <sample.h>
#include "sample.h"
Якщо ім'я файлу поміщене у кутові дужки, то пошук файлу здійснюватиметься в одному або декількох спеціальних каталогах, визначених конкретною реалізацією.
Якщо ж ім'я файлу поміщене в лапки, пошук файлу здійснюється, як правило, у поточному каталозі (що також визначено конкретною реалізацією). У багатьох випадках це означає пошук поточного робочого каталога. Якщо заданий файл не знайдено, то пошук повторюється з використанням першого способу (неначебто ім'я файлу було поміщено у кутові дужки). Щоб ознайомитися з подробицями, пов'язаними з різною обробкою директиви #include у разі використання кутових дужок і подвійних лапок, зверніться до настанови користувача, яка додається до Вашого компілятора. Настанови #include можуть бути вкладеними в інші поміщені файли.
Директиви умовного компілювання
Існують директиви, які дають змогу вибірково компілювати частини початкового коду програми. Цей процес, названий умовною компіляцією, широко використовують комерційні фірми з розроблення програмного забезпечення, які створюють і підтримують багато різних версій однієї програми.
Директиви #if, #else, #elif і #endif. Головна ідея полягає у тому, що коли вираз, який знаходиться після директиви #if виявляється істинним, то буде скомпільований програмний код, розташований між нею і директивою #endif; інакше цей програмний код буде опущений. Директиву #endif використовують для позначення кінця блоку #if.
Директиви #if, #ifdef, #ifndef, #else, #elif і #endif — це директиви умовного компілювання.
Загальна форма запису директиви #if має такий вигляд:
#if константний_вираз
послідовність настанов #endif
Якщо константний_вираз є істинним, то програмний код, розташований безпосередньо за цією директивою, буде скомпільований. Розглянемо такий приклад.
Код програми 13.4. Демонстрація механізму використання директиви #if #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define МАХ 100
int mainO
{
#if МАХ>10
cout«"Потрібна додаткова память" « endl;
#endif
II...
getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані повідомлення Потрібна додаткова пам'ять оскільки, як визначено у програмі, значення константи МАХ більше 10. Цей приклад ілюструє важливий момент: вираз, який знаходиться після директиви #if, обчислюється при компілюванні. Отже, воно повинно містити тільки ідентифікатори, які були заздалегідь визначені, або константи. Використання ж змінних тут виключене.
Поведінка директиви #else багато в чому подібна до поведінки настанови else, яка є частиною мови програмування C++: вона визначає альтернативу на випадок невиконання директиви #if. Щоб показати, як працює директива #else, скористаємося попереднім прикладом, дещо його розширивши.
Код програми 13.5. Демонстрація механізму використання директив #if / #else #include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define МАХ 6
int mainO
{
#if МАХ>10
cout«"Потрібна додаткова пам'ять" « endl;);
#else
cout«"Достатньо наявної пам'яті"« endl;
#endif
II...
getchO; return 0;
}
У цьому коді програми для імені МАХ визначено значення, яке менше 10, тому #іТ-гілка коду програми не відкомпілюється, проте відкомпілюєгься альтернативна #еІзе-гілка. Як наслідок, відобразиться повідомлення:
Достатньо наявної пам'яті.
Зверніть увагу на те, що директиву #else використовують для індикації одночасно як кінця #іТ-блоку, так і початку #еІзе-блоку. У цьому є логічна необхідність, оскільки тільки одна директива #endif може бути пов'язана з директивою #if.
Директива #elif еквівалентна зв'язці настанов else-if і використовують для формування багатоланкової схеми if-else-if, яка представляє декілька варіантів компілювання. Після директиви #elif повинен знаходитися константний вираз. Якщо цей вираз істинний, то наступний блок коду програми відкомпілюєгься, і ніякі інші #elif-BHpa3H не тестуватимуться або не компілюватимуться. Інакше буде перевірений наступний по черзі #elif-BHpa3. Ось як виглядає загальний формат використання директиви #elif:
#if вираз
послідовність настанов #elif вираз 1
послідовність настанов #elif вираз 2
послідовність настанов #elif вираз З
послідовність настанов
її...
#elif вираз N
послідовність настанов #endif
Наприклад, наведений нижче фрагмент коду програми використовує ідентифікатор COMPILED_BY, який дає змогу визначити, хто компілює програму:
#define JOHN 0 #define BOB 1 #define TOM 2
#define COMPILED_BY JOHN
#if COMPILED_BY == JOHN char Show[] = "John";
#elif COMPILED_BY == BOB char ShowQ = "Bob";
#else
char ShowQ = "Tom";
#endif
Директиви #if і #elif можуть бути вкладеними. У цьому випадку директива #endif, #else або #elif зв'язується з найближчою директивою #if або #elif. Наприклад, такий фрагмент коду програми є абсолютно допустимим:
#if COMPILED_BY == BOB #if DEBUG == FULL int port = 198;
#elif DEBUG == PARTIAL int port = 200;
#endif
#else
cout«"Борис повинен скомпілювати код"
«"для відлагодження виведення даних" « endl;
#endif
Директиви #ifdef і #ifndef. Ці директиви пропонують ще два варіанти умовного компілювання, які можна виразити як "якщо визначено" і "якщо не визначено" відповідно. Загальний формат використання директиви #ifdef такий:
#ifdef макроім'я
послідовність настанов #endif
Якщо макроім'я заздалегідь визначено за допомогою якої-небудь директиви #define, то послідовність настанов, розташована між директивами #ifdef і #endif, буде скомпільована.
Загальний формат використання директиви #ifndef такий:
#ifndef макроім'я
послідовність настанов #endif
Якщо макроім'я не визначене за допомогою якої-небудь директиви #define, то послідовність настанов, розташована між директивами #ifdef і #endif, буде скомпільована.
Як директива #ifdef, так і директива #ifndef може мати директиву #else або #elif. Розглянемо такий приклад.
#include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define ТОМ
int mainO
{
#ifdef ТОМ
cout«"Програміст Том" « endl;
#else
cout«"Програміст невідомий" « endl;
#endif
#ifndef RALPH
cout«"Ім'я RALPH не визначене" « endl;
#endif getchO; return 0;
}
Внаслідок виконання ця програма відображає на екрані такі результати: Програміст Том.
Ім'я RALPH не визначене.
Але якби ідентифікатор ТОМ не було визначено, то результат виконання цієї програми виглядав би так:
Програміст невідомий.
Ім'я RALPH не визначене.
Bapmoa нати! Директиви ttifdef і #ifndef можна вкладати так само, як і директиви т
Директива #undef. Цю директиву використовують для видалення попереднього визначення певного макроімені. Її загальний формат є таким:
#undef макроім'я Розглянемо такий приклад:
#define TIMEOUT 100 #define WAIT 0
II...
#undef TIMEOUT #undefWAIT
У цих записах імена TIMEOUT і WAIT визначені доти, доки не виконається директива #undef.
Основне призначення директиви #undef - дати змогу локалізувати макроімена для тих частин коду програми, в яких вони потрібні.
Використання оператора defined. Окрім директиви #ifdef, існує ще один спосіб з'ясувати, чи визначене у програмі певне макроім'я. Для цього можна використовувати директиву #if у поєднанні з оператором часу компілювання defined. Наприклад, щоб дізнатися, чи визначено макроім'я MYFILE, можна використовувати одну з таких команд препроцесорного оброблення:
#if defined MYFILE
або
#ifdef MYFILE
У разі потреби, щоб реверсувати умову перевірки, операторові defined може передувати символ Наприклад, такий фрагмент коду програми відкомпі- люється тільки у тому випадку, якщо макроім'я DEBUG не визначене:
#if Idefined DEBUG
cout«"Остаточна версія!" « endl;
#endif
Про значення препроцесора. Як ми уже зазначали раніше, препроцесор мови програмування C++ - прямий спадкоємець препроцесора мови С, до того ж без жодних удосконалень. Проте його вагомість у мові програмування C++ набагато менша, ніж препроцесора у мові С. Йдеться про те, що багато завдань, які виконує препроцесор у мові С, реалізовані мовою C++ у вигляді елементів мови. Страус- труп тим самим виразив свій намір зробити функції препроцесора непотрібними, щоб врешті-решт від нього можна було зовсім звільнити мову.
На цьому етапі препроцесор вже частково надлишковий. Наприклад, дві найбільш використовувані властивості директиви #define було замінено настановами мови C++. Зокрема, її здатність створювати константне значення і визначати мак- ровизначення, що діє подібно до функцій, тепер абсолютно надлишкова. У мові програмування C++ є ефективніші засоби для виконання цих завдань. Для створення константи достатньо визначити const-змінну. А із створенням вбудованої (що підставляється) функції легко справляється специфікатор inline. Обидва ці засоби краще працюють, ніж відповідні механізми директиви #define.
Наведемо ще один приклад заміни елементів препроцесора елементами мови. Він пов'язаний з використанням однорядкового коментарю. Одна з причин його створення - дозволити "перетворення" коду програми у коментар. Як уже зазначалося вище, коментар, що використовує /*...*/-стиль, не може бути вкладеним. Це означає, що фрагменти коду програми, що містять /*...*/-коментарі, одним махом "перетворити на коментар" не можна. Але це можна зробити з //-коментарями, оточивши їх /*...*/-символами коментарю. Можливість "перетворення" коду програми у коментар робить використання таких директив умовного компілювання, як #ifdef, частково надлишковим.
Директива #1іпе. Цю директиву використовують для зміни вмісту псевдоз-
мінних . LINE і FILE_ _, які є зарезервованими ідентифікаторами (макроімена-
ми). Псевдозмінна LINE містить номер скомпільованого рядка, а псевдозмінна
FILE _ - ім'я компільованого файлу. Базова форма запису цієї команди має такий
вигляд:
#Ііпе номер "ім'я_файлу"
У цьому записі номер - це будь-яке позитивне ціле число, а ім'я_файлу - будь- який допустимий ідентифікатор файлу. Значення елемента номер стає номером поточного початкового рядка, а значення елемента ім'я_файлу - іменем початкового файлу. Ім'я файлу - елемент необов'язковий. Директива #1іпе використовується, в основному, з метою відлагодження і у спеціальних додатках.
Директива #ІІпе змінює вміст псевдозмінних LINE__ і FILE .
Наприклад, наведений нижче код програми зобов'язує починати рахунок рядків з позиції 200. Настанова cout відображає номер 202, оскільки це третій рядок у програмі після директивної настанови #1іпе 200.
#include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#Ііпе 200 // Встановлюємо лічильник рядків, що дорівнює 200.
int mainO // Цей рядок зараз має номер 200.
{ //Номер цього рядка дорівнює 201. cout« LINE ; //Тут виводиться номер 202.
getchO; return 0;
}
Директива #pragma. Робота цієї директиви залежить від конкретної реалізації компілятора. Вона дає змогу видавати компіляторові різні настанови, передбачені творцем компілятора. Загальний формат його використання такий:
#pragma ім'я
У цьому записі елемент ім'я представляє ім'я бажаної #ргадта-настанови. Якщо вказане ім'я не розпізнається компілятором, директива #pragma просто ігнорується без повідомлення про помилку.
Директива #pragma залежить від конкретної реалізації компілятора.
Нео! хідноапам ятати'.аДля отримання детальної інформації про можливі варіанти використання директиви #pragma варто звернутися до системної документації з використовуваного Вами компілятора. Ви можете знайти для себе дуже корисну інформацію. Звичайно #ргадта-настанови дають змогу визначити, які застережні повідомлення видає компілятор, як генерується програмний код і які бібліотеки компонуються з Вашими програмами.
Оператори препроцесора і "##"
У мові програмування C++ передбачено підтримку двох операторів препроцесора: "#" і "##". Ці оператори використовують спільно з директивою #define. Оператор "#" перетворить наступний за ним аргумент у рядок, поміщений у лапки. Розглянемо, наприклад, такий код програми:
#include <iostream> // Для потокового введення-виведення
using namespace std; // Використання стандартного простору імен
#define mkstr(s) # s