Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
+ООП_Навч_посібник.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
6.58 Mб
Скачать

22.7.2. Зберігання рядків у інших контейнерах

Оскільки клас string визначає тип даних, можна створити контейнери, які міститимуть об'єкти типу string. Розглянемо, наприклад, вдаліший варіант програми-словника, яку було показано вище.

Код програми 22.20. Демонстрація механізму використання відображення string-об'єктів для створення словника

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

#include <map> // Для роботи з асоціативними контейнерами

#include <string> // Для роботи з рядками

using namespace std; // Використання стандартного простору імен

int main()

{

map<string.string> dictionary;

dictionary.insert(pair<string.string>(

"дім", "Місце мешкання."));

dictionary.insert(pair<string.string>(

"клавіатура", "Пристрій введення даних."));

dictionary.insert(pair<string.string>(

"програмування", "Процес розроблення програми."));

dictionary.insert(pair<string.

string>("STL", "Standard Template Library"));

string s;

cout << "Введіть слово: "; cin >> s;

map<string.string>::iterator p;

p = dictionary.find(s);

if(p != dictionary.end())

cout << "Визначення: " << p->second;

else

cout << "Такого слова у словнику немає.\n";

getch(); return 0;

}

Розділ 23. Особливості роботи препроцесора С++

Завершальний розділ навчального посібника присвячено особливостям роботи препроцесора C++. Препроцесор C++ – це частина компілятора, яка піддає Вашу програму різним текстовим перетворенням до реальної трансляції початкового коду програми в об'єктний. Програміст може давати препроцесору команди, звані директивами препроцесора (preprocessor directives), які, не будучи формальною частиною мови програмування C++, здатні розширити область дії його середовища програмування. Препроцесор C++ містить наступні директиви.

#define

#error

#include

#if

#else

#elif

#endif

#ifdef

#ifndef

#undef

#line

#pragma

Як бачите, всі директиви препроцесора починаються з символу "#". Тепер розглянемо кожну з них окремо.

Необхідно пам'ятати! Препроцесор C++ прямий нащадок препроцесора С, і деякі його засоби виявилися надмірними після введення у мові програмування C++ нових елементів. Проте він, як і раніше, є важливою частиною С++-середовища програмування.

23.1. Поняття про директиви препроцесора C++

23.1.1. Директива #define

Директива #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 I

#define TWO ONE+ONE

#define THREE ONE+TWO

Важливо розуміти, що макропідстановка – це просто заміна ідентифікатора відповідним рядком. Отже, якщо виникає потреба визначити стандартне повідомлення, використовується програмний код, подібний цьому.

#define GETFILE "Введіть ім'я файлу"

//...

Препроцесор замінить рядком "Введіть ім'я файлу" кожне входження ідентифікатора GETFILE. Для компілятора ця cout-настанова

cout << GETFILE;

насправді виглядає так.

cout << "Введіть ім'я файлу";

Ніякої текстової заміни не відбудеться, якщо ідентифікатор знаходиться в рядку, поміщеному в лапки. Наприклад, у процесі виконання наступного коду програми

#define GETFILE "Введіть ім'я файлу"

//...

cout << "GETFILE – це макроім'я\n";

на екрані буде відображена ця інформація

GETFILE – це макроім'я

а не ця:

Введіть ім'я файлу – це макроім'я

Якщо текстова послідовність не поміщається на рядку, її можна продовжити на наступній, поставивши зворотну косу межу у кінці рядка, як це показано у наведеному прикладі.

#define LONG_STRING "Це дуже довга послідовність, \

яка використовується як приклад."

Серед С++-програмістів прийнято використовувати для макроімен прописні букви. Ця домовленість дає змогу з першого погляду зрозуміти, що тут використовується макропідстановка. Окрім того, краще за все помістити всі директиви #define у початок файлу або включити в окремий файл, щоб не шукати їх потім за всією програмою.

Макропідстановки часто використовують для визначення "магічних чисел" програми. Наприклад, у Вас є програма, яка визначає деякий масив, і ряд функцій, які отримують доступ до нього. Замість "жорсткого" кодування розміру масиву за допомогою константи краще визначити ім'я, яке б представляло розмір, а потім використовувати це ім'я скрізь, де повинен знаходитися розмір масиву. Тоді, якщо цей розмір доведеться змінити, Вам достатньо буде внести тільки одну зміну, а потім перекомпілювати програму. Розглянемо такий приклад.

#define Max_size 100

//...

float balance[Max_size];

double index[Max_size];

int num_emp[Max_size];

Необхідно пам'ятати! Важливо пам'ятати, що у мові програмування C++ передбачено ще один спосіб визначення констант, який полягає у використанні специфікатора const. Проте багато програмістів "прийшли" у мові програмування C++ з С-среди, де для цих потреб звичайно використовувалася директива #define. Тому Вам ще часто доведеться з нею стикатися у С++-коді програми.

Макровизначення, що діють як функції

Директива #define має ще одне призначення: макроім'я може використовуватися з аргументами. Під час кожного входженні макроімені пов'язані з ним аргументи замінюються реальними аргументами, вказаними у коді програми. Такі макровизначення діють подібно до функцій. Розглянемо такий приклад.

Код програми 23.1. Демонстрація механізму використання "функціональних" макровизначень

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

#define MIN(а, b) (((a)<(b))? а: b)

int main()

{

int x, y;

x = 10; y = 20;

cout << "Мінімум дорівнює: " << MIN(x, y);

getch(); return 0;

}

У процесі компілювання цієї програми вираз, визначено ідентифікатором MIN(а, b), буде замінено, але х і у розглядатимуться як операнди. Це означає, що з out-настанова після компілювання виглядатиме так:

cout << "Мінімум дорівнює: " << (((х)<(у))? х: у);

По суті, таке макровизначення є способом визначити функцію, яка замість виклику дає змогу розкрити свій код у рядку.

Макровизначення, що діють як функції, – це макровизначення, які приймають аргументи. Круглі дужки, що здаються надмірними, в які поміщено макровизначення MIN, необхідні, щоб гарантувати правильне сприйняття компілятором замінюваного виразу. Насправді додаткові круглі дужки повинні застосовуватися практично до всіх макровизначень, що діють подібно до функцій. Потрібно завжди дуже уважно відноситися до визначення таких макросів; інакше можливо отримання несподіваних результатів. Розглянемо, наприклад, цю коротку програму, яка використовує макрос для визначення парності значення.

Код програми 23.2. Демонстрація неправильної роботи програми

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

#define EVEN(a) a%2==0? 1: 0

int main()

{

if(EVEN(9 + 1)) cout << "парне число";

else cout << "непарне число ";

getch(); return 0;

}

Ця програма не працюватиме коректно, оскільки не забезпечена правильна підстановка значень. У процесі компілювання вираз EVEN(9 + 1) буде замінений таким чином.

9+1 %2==0 ? 1 : 0

Нагадаю, що оператор "%" має вищий пріоритет, ніж оператор додавання "+". Це означає, що спочатку виконається операція ділення за модулем (%) для числа 1, а потім її результат буде складний з числом 9, що (звичайно ж) не рівно 0. Щоб виправити помилку, достатньо помістити у круглі дужки аргумент, а в макровизначенні EVEN, як це показано в наступній (виправленій) версії тієї ж самої програми.

Код програми 23.3. Демонстрація коректної роботи програми

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

#define EVEN(a) (a)%2==0? 1: 0

int main()

{

if(EVEN(9 + 1)) cout << "парне число";

else cout << "непарне число";

getch(); return 0;

}

Тепер сума 9+1 обчислюється до виконання операції ділення за модулем. У загальному випадку краще завжди брати параметри макровизначення у круглі дужки, щоб уникнути непередбачених результатів, подібних описаному вище.

Використання макровизначень замість справжніх функцій має одну істотну перевага: оскільки програмний код макровизначення розширюється в рядку, і немає ніяких витрат системних ресурсів на виклик функції, швидкість роботи Вашої програми буде вища порівняно із застосуванням звичайної функції. Але підвищення швидкості є платнею за збільшення розміру програми (через дублювання коду функції).

Необхідно пам'ятати! Незважаючи на те, що макровизначення все ще трапляється у С++-коді програми, макросах, що діють подібно до функцій, можна замінити специфікатором inline, який справляється з тією ж роллю краще і безпечніше. (Пригадайте: специфікатор inline забезпечує замість виклику функції розширення її тіла в рядку). Окрім того, inline-функції не вимагають додаткових круглих дужок, без яких не можуть обійтися макровизначення. Проте макроси, що діють подібно до функцій, все ще залишаються частиною С++-програм, оскільки багато С/С++-програмісти продовжують використовувати їх за звичкою.