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

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-атрибуту об'єкта.

  1. Оператор неполіморфного приведення типів 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;

}

  1. Оператор перетворення типу 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 перетворить покажчик р у ці- лочисельне значення. Внаслідок такого перетворення відбувається фундаменталь­на зміну типу.

  1. Порівняння звичайної операції приведення типів з новими чотирма cast-операторами

Можливо, комусь з Вас могло б видатися, що описані вище чотири cast-one - ратори повністю замінюють традиційну операцію приведення типів. На запитан­ня: "Чи варто завжди замість звичайної операції приведення типів використовува­ти новіші засоби?". Відповімо - загального правила для всіх програмістів не існує. Оскільки нові оператори були створені для підвищення безпеки достатньо ризико­ваної операції приведення одного типу даних до іншого, багато С++-програмістів переконані у тому, що їх необхідно використовувати виключно з цією метою. І тут важко що-небудь заперечити. Інші ж програмісти вважають, що, оскільки тра­диційна операція приведення типів надійно слугувала їм протягом багатьох років, то від неї не варто так легко відмовлятися. Наприклад, для виконання простих і відносно безпечних операцій приведення типів (як ті, що потрібні під час виклику функцій введення-виведення read() і writeO, описаних у попередньому розділі) "ста­рий добрий" засіб цілком прийнятний.

Існує ще одна точка зору, з якою важко не погодитися: у процесі виконання операцій приведення поліморфних типів безумовно варто використовувати опера­тор dynamic_cast.

Розділ 11. ПОНЯТТЯ ПРО ПРОСТОРИ ІМЕН ТА ІНШІ ЕФЕКТИВНІ ПРОГРАМНІ ЗАСОБИ

У цьому розділі буде описано основні поняття про простори імен і такі ефек­тивні програмні засоби, як explicit-конструктори, покажчики на функції, static-чле- ни, const-функції-члени, альтернативний синтаксис ініціалізації членів-даних кла­су, оператори вказання на члени класу, ключове слово asm, специфікація компо­нування і функції перетворення.

  1. Особливості організації простору імен

Простори імен стисло розглянуто у розд. 2 [9]. Там було сказано, що вони да­ють змогу локалізувати імена ідентифікаторів, щоб уникнути конфліктних ситу­ацій з ними. У С++-середовищі програмування використовується величезна кіль­кість імен змінних, функцій та імен класів. До введення поняття простору імен всі ці імена конкурували між собою за пам'ять в глобальному просторі імен, що і бу­ло причиною виникнення багатьох конфліктів. Наприклад, якби у Вашій програмі було визначено функцію toupper(), то вона могла б (залежно від переліку парамет­рів) перевизначити стандартну бібліотечну функцію toupper(), оскільки обоє імен мали б зберігатися в глобальному просторі імен. Конфлікти з іменами виникали раніше також під час використання однією програмою декількох бібліотек сто­ронніх виробників. У цьому випадку ім'я, що є визначеним у одній бібліотеці, конфліктувало з таким самим іменем з іншої бібліотеки. Така ситуація особливо неприйнятна під час використання однойменних класів. Наприклад, якщо у Вашій програмі визначено клас VideoMode, і в бібліотеці, що використовуються Вашою програмою, визначено клас з таким самим іменем, то конфлікту не уникнути.

Простір імен визначає деяку декларативну область.

Для вирішення описаної проблеми було створено ключове слово namespace. Оскільки воно локалізує видимість оголошених у ньому імен, то це означає, що простір імен дає змогу використовувати одне і те саме ім'я в різних контекстах, не викликаючи при цьому конфлікту імен. Можливо, найбільше від нововведення "поталанило" С++-бібліотеці стандартних функцій. До появи ключового слова na­mespace (яке було, звичайно ж, єдиним) всю С++-бібліотеку було визначено в гло­бальному просторі імен. З настанням namespace-"epH" С++-бібліотека визначається у власному просторі імен, названий std, який значно знизив ймовірність виникнен­ня конфліктів імен. У своїй програмі програміст повинен створювати власні прос­тори імен, щоб локалізувати видимість тих імен, які, на його думку, можуть стати причиною конфлікту. Це особливо важливо, якщо Ви займаєтеся створенням біб­ліотек класів або функцій.

  1. Поняття про простори імен

Ключове слово 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....