Namespace CounterNameSpace 2 страница

Константна (const-) функція-член класу не може модифікувати об'єкт, який її викликав.

Щоб визначити функцію як const-члена класу, використовується формат, який представлено у наведеному нижче прикладі:

classaType { // Оголошення класового типу

int some_var;

public:

int fun_1() const; // const-функція-член

};

Як бачите, модифікатор const розташовується після оголошення переліку параметрів функції.

Мета оголошення функції як const-члена класу – не допустити модифікацію об'єкта, який її викликає. Для розуміння сказаного розглянемо наведену нижче програму.

Код програми 21.10. Демонстрація механізму використання const-функцій-членів

// Ця програма не скомпілюється.

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

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

class Demo {

int izm;

public:

int getIzm() const;

return izm; // Все гаразд

}

 

void setIzm(int x) const

{

izm = x; // помилка! }

};

 

int main()

{

Demo ob;

ob.setIzm(1900);

cout << ob.getIzm();

 

getch(); return 0;

}

Ця програма не скомпілюється, оскільки функцію setIzm() оголошено як const-член. Це означає, що їй не дозволено модифікувати той об'єкт, який її викликає. Її спроба змінити вміст змінної izm призводить до виникнення помилки. На відміну від функції setIzm(), функція getIzm() не намагається модифікувати змінну izm, і тому вона абсолютно прийнятна.

Можливі ситуації, коли потрібно, щоб const-функція могла змінити один або декілька членів класу, але ніяк не могла вплинути на інші. Це можна реалізувати за допомогою модифікатора mutable, який перевизначає атрибут функції const. Іншими словами, mutable-член може бути модифікований const-функцією-членом. Розглянемо такий приклад.

Код програми 21.11. Демонстрація механізму використання модифікатора mutable

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

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

class Demo {

mutable int izm;

int jzm;

public:

int getIzm() const { return izm; // Все гаразд }

 

void setIzm(int x) const

{

izm = x; // тепер все гаразд

};

 

// Наступна функція не скомпілюється.

void setJzm(int x) const

{

jzm = x; // Це, як і раніше, неправильно!

};

}

 

int main()

{

Demo A_ob;

A_ob.setIzm(1900);

cout << A_ob.getIzm();

 

getch(); return 0;

}

У цій програмі закритий член-даних izm визначено з використанням модифікатора mutable, тому його можна змінити за допомогою функції setIzm(). Проте змінна jzm не є mutable-членом, тому функції setJzm() не дозволяється модифікувати його значення.

21.6. Використання explicit-конструкторів

Для створення "неконвертованого" конструктора використовується специфікатор explicit.

У мові програмування C++ визначено ключове слово explicit, яке застосовується для оброблення спеціальних ситуацій, наприклад, під час використання конструкторів певних типів. Щоби зрозуміти призначення специфікатора explicit, спочатку розглянемо наведену нижче програму.

Код програми 21.12. Демонстрація механізму використання специфікатора explicit

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

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

 

class myClass { // Оголошення класового типу

int a;

public:

myClass(int x) { a = x;}

int getAzm() { return a;}

};

 

int main()

{

myClass A_ob(4);

cout << A_ob.getAzm();

 

getch(); return 0;

}

У цій програмі конструктор класу myClass приймає один параметр. Звернемо Вашу увагу на те, який оголошено об'єкт A_ob у функції main(). Значення 4, що задається у круглих дужках після імені A_ob, є аргументом, який передається параметру х конструктора myClass(), а параметр х, своєю чергою, використовується для ініціалізації члена а. Саме таким способом ми ініціалізуємо члени класу, як це було показано на початку цього навчального посібника. Проте існує і альтернативний варіант ініціалізації. Наприклад, у процесі виконання такої настанови член класу а також набуде значення 4.

myClass A_ob = 4; // Цей формат ініціалізації автоматично

// перетвориться у формат myClass(4).

Як зазначено у коментарі, цей формат ініціалізації автоматично перетвориться у виклик конструктора класу myClass, а число 4 використано як аргумент. Іншими словами, попередня настанова обробляється компілятором так, як вона була записана:

myClass A_ob(4);

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

Якщо програмісту не потрібно, щоб таке неявне перетворення мало місце, то можна запобігти цьому за допомогою специфікатора explicit. Ключове слово explicit застосовується тільки до конструкторів. Конструктор, визначений за допомогою специфікатора explicit, буде задіяний тільки у тому випадку, якщо для ініціалізації членів-даних класу використовується звичайний синтаксис конструктора. Ніяких автоматичних перетворень виконано не буде. Наприклад, оголошуючи конструктор класу myClass з використанням специфікатора explicit, ми, тим самим, відміняємо підтримку автоматичного перетворення типів. У цьому варіанті визначення класу функція myClass() оголошується як explicit-конструктор.

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

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

 

class myClass { // Оголошення класового типу

int a;

public:

explicit myClass(int x) { a = x;}

int getAzm() { return a;}

};

Тепер буде дозволено до застосування тільки конструктори, які задаються в такому форматі:

myClass A_ob(110);

Потреба неявного перетворення конструктора

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

Код програми 21.13. Демонстрація механізму використання неявного перетворення конструктора

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

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

 

class myClass { // Оголошення класового типу

int num;

public:

myClass(int izm) { num = izm;}

int getNum() { return num;}

};

 

int main()

{

myClass A_ob(10);

cout << A_ob.getNum() << endl; // Відображає 10

// Тепер використовуємо неявне перетворення для

// присвоєння нового значення.

A_ob = 1000;

cout << A_ob.getNum() << endl; // Відображає 1000

 

getch(); return 0;

}

Звернемо Вашу увагу на те, що нове значення присвоюється об'єкту A_ob за допомогою такої настанови:

A_ob = 1000;

Використання такого формату можливе завдяки опосередкованому перетворенню з типу int в тип myClass, яке створюється конструктором myClass(). Звичайно ж, якби конструктор myClass() було оголошено за допомогою специфікатора explicit, то попередня настанова не могла б виконатися.

21.7. Синтаксис ініціалізації членів-даних класу

У прикладах програм, наведених у попередніх розділах, члени-даних набували початкові значення у конструкторах своїх класів. Наприклад, наведений нижче код програми містить клас myClass, який містить два члени-даних numA і numB. Ці члени ініціалізувалися у конструкторі myClass().

Код програми 21.14. Демонстрація ініціалізації членів-даних з використанням конструкторів класу

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

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

class myClass { // Оголошення класового типу

int numA;

int numB;

public:

// Ініціалізація членів numA і numB у конструкторі myClass(),

використовуючи звичайний синтаксис. */

myClass(int x, int y) { numA = x; numB = y;}

int getNumA() { return numA;}

int getNumB() { return numB;}

};

 

int main()

{

myClass A_ob(7, 9), B_ob(5, 2);

cout << "Значення членів-даних об'єкта A_ob дорівнює "

<< A_ob.getNumB() << " і " << A_ob.getNumA() << endl;

cout << "Значення членів-даних об'єкта B_ob дорівнює "

<< B_ob.getNumB() << " і " << B_ob.getNumA() << endl;

 

getch(); return 0;

}

Результати виконання цієї програми є такими:

Значення членів-даних об'єкта A_ob дорівнює 9 і 7

Значення членів-даних об'єкта B_ob дорівнює 2 і 5

Присвоєння початкових значень членам-даних numA і numB у конструкторі, як це робиться у конструкторі myClass(), – звичайна практика, яка застосовується для багатьох класів. Але цей метод придатний не для всіх випадків. Наприклад, якби члени numA і numB були задані як const-змінні, тобто так:

class myClass { // Оголошення класового типу

const int numA; // const-член

const int numB; // const-член,

то їм не можна було б присвоїти значення за допомогою конструктора класу myClass, оскільки const-змінні повинні ініціалізуватися одноразово, після чого їм вже не можна надати інші значення. Такі проблеми виникають під час використання посилальних членів, які повинні ініціалізуватися, і під час використання членів класу, які не мають конструкторів за замовчуванням. Для вирішення проблем такого роду у мові програмування C++ передбачено підтримку альтернативного синтаксису ініціалізації членів-даних класу, який дає змогу присвоювати їм початкові значення під час створення об'єкта класу.

Синтаксис ініціалізації членів-даних класу аналогічний тому, який використовують для виклику конструктора базового класу. Ось як виглядає загальний формат такої ініціалізації:

constructor (перелік_аргументів): член1(ініціалізація),

член2(ініціалізація),

//...

члениN (ініціалізація)

{

// Тіло конструктора

}

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

Нижче представлена попередня програма, але перероблена так, щоби члени numA і numB були оголошені з використанням модифікатора const, і набували свої початкові значення за допомогою альтернативного синтаксису ініціалізації членів-даних класу.

Код програми 21.15. Демонстрація способу оголошення членів-даних класу з використанням модифікатора const

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

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

 

class myClass { // Оголошення класового типу

const int numA; // const-член

const int numB; // const-член

public:

// Ініціалізація членів numA і numB з використанням

// альтернативного синтаксису ініціалізації.

myClass(int x, int y): numA(x), numB(y) { }

int getNumA() { return numA;}

int getNumB() { return numB;}

};

 

int main()

{

myClass A_ob(7, 9), B_ob(5, 2);

 

cout << "Значення членів-даних об'єкта A_ob дорівнює "

<< A_ob.getNumB() << " і " << A_ob.getNumA() << endl;

 

cout << "Значення членів-даних об'єкта оb2 дорівнює "

<< B_ob.getNumBf) << " і " << B_ob.getNumA() << endl;

 

getch(); return 0;

}

Ця програма відображає на моніторі такі ж результати, як і її попередня версія. Проте зверніть увагу на те, як ініціалізовано члени numA і numB:

myClass(int x, int y): numA(x), numB(y) { }

У цьому записі член даних numA ініціалізувався значенням, переданим у аргументі х, а член numB – значенням, переданим у аргументі y. І хоча члени numA і numB зараз визначені як const-змінні, проте вони можуть набути свої початкові значення під час створення об'єкта класу myClass, оскільки тут використовується альтернативний синтаксис ініціалізації членів-даних класу.

21.8. Використання ключового слова asm

Незважаючи на те, що мова програмування C++ – всеосяжна і могутній засіб для створення сучасних програмних продуктів, проте трапляються ситуації, оброблення яких для неї виявляється дуже скрутним[86]. Щоб справитися з подібними спеціальними ситуаціями, мова C++ надає засіб, який дає змогу увійти до коду програми, написаного мовою Асемблер, абсолютно ігноруючи С++-компілятор. Цим засобом і є настанова asm, використовуючи яку можна вбудувати Асемблерний код програми безпосередньо у С++-програму. Цей програмний код скомпілюється без будь-яких змін і стане частиною коду Вашої програми, починаючи з місця знаходження настанови asm.

За допомогою ключового слова asm у С++-програму вбудовується програмний код, написаний мовою Асемблер.

Загальний формат використання ключового слова asm має такий вигляд:

asm ("код");

У цьому записі елемент код означає настанову, написану мовою Асемблер, яка буде вбудована у С++-програму. При цьому деякі компілятори також дають змогу використовувати і інші формати запису настанови asm:

asm настанова;

asm настанова newline

asm {

послідовність настанов

}

У цьому записі елемент настанова означає будь-яку допустиму настанову мови Асемблер. Оскільки використання настанови asm залежить від конкретної реалізації середовища програмування, то за подробицями реалізації потрібно звернутися до документації, що додається до Вашого компілятора.

На момент написання цього навчального посібника у середовищі Visual C++ (Microsoft) для вбудовування коду програми, написаного мовою Асемблер, пропонувалося використовувати настанову __asm. У всьому іншому цей формат аналогічний опису настанови asm.

Варто знати! Для використання настанови asm необхідно володіти доскональними знаннями мови Асемблер. Якщо Ви не вважаєте себе фахівцем з цієї мови, то краще поки що уникати використання настанови asm, оскільки необережне її застосування може спричинити важкі наслідки для Вашої системи.

21.9. Специфікатор компонування функцій

У мові програмування C++ можна визначити, як функція зв'язується з Вашою програмою. За замовчуванням функції компонуються як С++-функції. Але, використовуючи специфікацію компонування, можна забезпечити компонування функцій, написаних іншими мовами програмування. Загальний формат специфікатора компонування має такий вигляд:

extern "мова" прототип_функції

У цьому записі елемент мова означає потрібну мову програмування. Всі С++-компілятори підтримують як С-, так і С++-компонування. Деякі компілятори також дають змогу використовувати специфікатори компонування для таких мов, як Fortran, Pascal або Visual Basic[87].

Специфікатор компонування дає змогу визначити спосіб компонування функції.

Наведений нижче код програми дає змогу скомпонувати функцію myCfunc() як С-функцію.

Код програми 21.16. Демонстрація механізму застосування специфікатора компонування функцій

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

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

extern "C" void myCfunc();

 

int main()

{

myCfunc();

 

getch(); return 0;

}

 

// Ця функція буде скомпонована як С-функція.

void myCfunc()

{

cout << "Ця функція скомпонована як С-функція.\n";

}

Необхідно пам'ятати! Ключове слово extern – необхідна складова специфікації компонування. Понад це, специфікація компонування повинна бути глобальною; її не можна використовувати в тілі якої-небудь функції.

Використовуючи наступний формат специфікації компонування, можна задати не одну, а відразу декілька функцій.

extern "мова" {

прототипи_функцій

}

Специфікації компонування використовуються досить рідко, і Вам, можливо, ніколи не доведеться їх застосовувати. Основне їх призначення – дати змогу застосування у С++-про­гра­мах кодів програм, написаних іншими організаціями мовами, відмінними від мови C++.

21.10. Оператори вказання на члени класу ".*" і "->"

У мові програмування C++ передбачено можливість згенерувати покажчик спеціального типу, який "посилається" не на конкретний примірник члена в об'єкті, а на члена класу взагалі. Покажчик такого типу називається покажчиком на члена класу (pointer-to-member). Це – не звичайний С++-покажчик. Цей спеціальний покажчик забезпечує тільки відповідний зсув у об'єкті, який дає змогу виявити потрібного члена класу. Оскільки покажчики на члени – не справжні покажчики, то до них не можна застосовувати оператори "." і "->". Для отримання доступу до члена класу через покажчик на член необхідно використовувати спеціальні оператори ".*" і "->".

Оператори вказання на члени класу дають змогу отримати доступ до члена класу через покажчик на цього члена.

Якщо ідея, яку викладено у попередньому абзаці, Вам видалася трохи "невиразною", то наведений нижче приклад допоможе її з'ясувати. У процесі виконання цієї програми відображається сума чисел від 1 до 7. Тут доступ до членів класу myClass(функції sum_it() і змінної sum) реалізується шляхом використання покажчиків на члени.

Код програми 21.17. Демонстрація механізму використання покажчиків на члени класу (початкова версія)

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

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

class myClass { // Оголошення класового типу

public:

int sum;

void myClass::sum_it(int x);

};

 

void myClass::sum_it(int x)

{

int i;

 

sum = 0;

for(i=x; i; i--) sum += i;

}

 

int main()

{

int myClass::*dp; // Покажчик на int-члена класу

void (myClass::*fp)(int x); // Покажчик на функцію-члена

 

myClass C_ob;

 

dp = myClass::sum; // Отримуємо адресу члена даних

fp = &myClass::sum_it; // Отримуємо адресу функції-члена класу

 

(C_ob.*fp)(7); // Обчислюємо суму чисел від 1 до 7

cout << "Сума чисел від 1 до 7 дорівнює " << C_ob.*dp;

 

getch(); return 0;

}

Результат виконання цієї програми є таким:

Сума чисел від 1 до 7 дорівнює 28

У функції main() створюється два члени-покажчики: dp (для вказівки на змінну sum) і fp (для вказівки на функцію sum_it()). Звернемо Вашу увагу на синтаксис кожного оголошення. Для уточнення класу використовують оператор дозволу контексту (оператор дозволу області видимості). Програма також створює об'єкт типу myClass з іменем C_ob.

Потім програма отримує адреси змінної sum і функції sum_it() і присвоює їх покажчикам відповідно dp і fp. Як ми вже зазначали вище, ці адреси насправді| є тільки зсувами в об'єкті типу myClass, згідно з якими можна знайти змінну sum і функцію sum_it(). Потім програма використовує покажчик на функцію fp, щоб викликати функцію sum_it() для об'єкта C_ob. Наявність додаткових круглих дужок пояснюється необхідністю коректного застосування оператора ".*". Нарешті, програма відображає значення суми чисел, отримуючи доступ до змінної sum об'єкта C_ob через покажчик dp.

Під час доступу до члена об'єкта за допомогою об'єкта або посилання на нього необхідно використовувати оператор ".*". Але, якщо для цього використовується покажчик на об'єкт, то потрібно використовувати оператор "->*", як показано у цій версії попередньої програми.

Код програми 21.18. Демонстрація механізму використання покажчиків на члени класу (модифікована версія)

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

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

class myClass { // Оголошення класового типу

public:

int sum;

void myClass::sum_it(int x);

};

 

void myClass::sum_it(int x)

{

int i;

 

sum = 0;

for(i=x; i; i--) sum += i;

}

 

int main()

{

int myClass::*dp; // Покажчик на int-члена класу

void (myClass::*fp)(int x); // Покажчик на функцію-члена

myClass *c, D_ob; // Член c зараз -- покажчик на об'єкт

 

c = &D_ob; // Присвоюємо покажчику c адресу об'єкта

dp = &myClass::sum; // Отримуємо адресу члена даних sum

fp = &myClass::sum_it; // Отримуємо адресу функції sum_it()

 

(c->*fp)(7); // Тепер використовуємо оператор "->*" для

// виклику функції sum_it().

 

cout << "Сума чисел від 1 до 7 дорівнює " << c->*dp; // ->*

 

getch(); return 0;

}

У цій версії змінна c оголошується як покажчик на об'єкт типу myClass, а для доступу до члена даних sum і функції-члена sum_it() використовують оператор "->*".

Необхідно пам'ятати! Оператори вказання на члени класу призначені для спеціальних випадків, тому їх не варто використовувати для розв'язання звичайних повсякденних задач програмування.

21.11. Створення функцій перетворення

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

operator type() {return value;}

У цьому записі елемент type – новий тип, який є метою нашого перетворення, а елемент value – значення після перетворення. Функція перетворення повинна бути членом класу, для якого вона визначається.

Функція перетворення автоматично перетворить тип класу в інший тип.

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

operator int() { return x * y * z;}

Тепер розглянемо програму, яка ілюструє роботу функції перетворення.

Код програми 21.19. Демонстрація механізму використання функції перетворення

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

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

 

class kooClass { // Оголошення класового типу

int x, y, z; // Тривимірні координати

public:

kooClass(int a, int b, int c) {x = a; y = b; z = c;}

 

kooClass operator+(kooClass op2);

friend ostream &operator<<(ostream &stream, kooClass &obj);

operator int() {return x * y * z;}

};

 

// Відображення тривимірних координат x, y, z – функція виведення даних

// для класу kooClass.

ostream &operator<<(ostream &stream, kooClass &obj)

{

stream << obj.x << ", ";

stream << obj.y << ", ";

stream << obj.z << "\n";

 

return stream; // Повертає посилання на параметр stream

}

 

kooClass kooClass::operator+(kooClass op2)

{

kooClass tmp(0, 0, 0);

 

tmp.x = x + op2.x;

tmp.y = y + op2.y;

tmp.z = z + op2.z;

 

return tmp; // Повертає модифікований тимчасовий об'єкт

}

 

int main()

{

kooClass A_ob(1, 2, 3), A_ob(2, 3, 4);