ТА_Методички / Lec_13
.pdf#include <iostream> using namespace std; class c1 {
int id; public: int i ;
c1(int i) ; ~c1 () ;
void neg(c1 &o) { o.i = -o.i; } // Тимчасовий об'єкт не створюється
};
c1::c1(int num)
{
cout << "Створення об'єкта " << num << "\n"; id = num;
}
c1::~c1()
{
cout << "Знищення об'єкта " << id << "\n";
}
int main() { c1 o(l);
31/54
o.i = 10; о.neg(о) ;
cout << o.i << "\n"; return 0;
}
Програма виводить на екран наступні результати. Створення об'єкта 1 -10
Знищення об'єкта 1
Як бачимо, деструктор класу c1 викликається лише один раз. Якби об'єкт о передавався за значенням, всередині функції neg() був би створений другий об'єкт, для знищення якого довелося б викликати деструктор.
У функції neg() для доступу до члена об'єкта використовується оператор ".", оскільки оператор "->" застосовується лише до вказівників.
При передачі параметрів по посиланню варто пам'ятати, що всі зміни, які відбуваються з параметром всередині функції, відбиваються в модулі, що викликає.
І, нарешті, передача по посиланню відбувається швидше, ніж передача за значенням. Виключенням із цього правила можуть бути лише мікроскопічні об'єкти. Аргументи, як правило, поміщаються в стек. Внаслідок цього процес заштовхування більших об'єктів у стек і виштовхування їх звідти займає багато часу.
32/54
Повернення посилань
Функція може повертати посилання на об'єкт. Таким чином, виклик функції може стояти у лівій частині оператора присвоювання! Розглянемо наступний приклад.
#include <iostream> using namespace std;
char &replace(int i); // Повернення посилання
char s[80] = "Загальний привіт"; int main ()
replace(9) = 'X'; |
// |
Вставляємо символ. Х між словами |
|
cout |
<< s; |
// |
"Загальний" і "привіт" , |
|
|
||
return 0; } |
i) |
|
|
char |
&replace(int |
|
|
return s[i];
}
33/54
Ця програма вставляє символ X замість пробілу між словами "Загальний" і "привіт", тобто виводить на екран рядок "ЗагальнийХпривіт". Розглянемо, як це відбувається. По-перше, функція replace () повертає посилання на символ, що входить у рядок s, індекс якого заданий змінною i. Потім у функції main() за допомогою цього посилання відповідному елементу рядка присвоюється символ X.
При поверненні посилання варто мати на увазі, що по завершенні роботи функції повертається об’єкт, що, не виходить із області видимості.
Незалежні посилання
Ми розглянули дві ситуації, у яких застосовуються посилання: передача параметрів і повернення значення функції. Однак посилання можна оголошувати просто як змінну. Такі посилання називаються незалежними.
Незалежні посилання є псевдонімами об'єктів. При оголошенні незалежних посилань вони обов'язково ініціалізуються, оскільки після ініціалізації їх не можна зв'язувати з іншими об'єктами. Отже, єдиний спосіб задати значення посилання - ініціалізувати його. (У мові C++ ініціалізація суттєво відрізняється від оператора присвоювання.)
Розглянемо програму, у якій застосовуються незалежні посилання.
34/54
iinclude <iostream> using namespace std;
int |
main() { |
|
int |
a; |
|
int |
&ref= a; |
// Незалежне посилання |
a = |
10; |
|
cout << a << " " << ref << "\n"; |
||
ref |
=100; |
|
cout << a << " " << ref << "\n"; |
||
int |
b = 19; |
// Присвоюємо змінній а значення змінної b |
ref |
= b; |
|
cout << a << " " << ref << "\n"; |
||
ref--; |
// Зменшення на одиницю змінної а. |
|
cout << a << " " << ref << "\n"; |
||
return 0; |
|
|
} |
|
|
Результат роботи програми має такий вигляд. |
||
10 |
10 |
|
100 100 |
|
|
19 |
19 |
|
18 |
18 |
|
35/54
Фактично незалежні посилання не грають значної ролі в програмуванні, оскільки вони являють собою лише друге ім'я іншої змінної, а об'єкти, що мають кілька імен, свідчать про погану організацію програми.
Посилання на довільні типи
Посилання на базовий клас, як і вказівник, може використовуватися для доступу до об'єктів похідного класу. Найчастіше ця можливість використовується при передачі параметрів функцій. Параметру, який передається за допомогою посилання на базовий клас, можна присвоювати об'єкти як базового, так і похідних класів.
Обмеження на посилання
У мові C++ існують обмеження на роботу з посиланнями. Наприклад, посилання не можуть зв'язуватися з іншими посиланнями. Інакше кажучи, посилання не має адреси.
Крім того, неможливо створити вказівник на посилання. До бітового поля також не можна звернутися за допомогою посилання.
Незалежні посилання повинні бути ініціалізовані в момент свого оголошення. Нульового посилання не існує.
36/54
Стиль
Оголошуючи вказівники й посилання, деякі програмісти використовують один і той же стиль, зв'язуючи символи * і & з ім'ям типу, а не зі змінною.
Розглянемо два еквівалентних оголошення.
int& p; // Символ & пов'язаний з типом int &p; // Символ & пов'язаний зі змінною
Зв'язування символів * або & з типом відбиває бажання деяких програмістів створити окремий тип вказівників. Однак це може викликати непорозуміння, оскільки символи * і & не поширюються на список змінних. Наприклад, у наступному оголошенні створюється один цілочисельний вказівник, а не два.
int* a, b;
тут b - цілочисельна змінна, а не вказівник, оскільки відповідно до правил мови C++ символ * (як і символ &) зв'язується з окремою змінною, а не з її типом.
Причина непорозуміння полягає в тім, що візуально це оголошення виглядає так, начебто в ньому оголошуються два цілочисельних вказівники, у
37/54
той час як вказівником є лише змінна а. Це оголошення може ввести в оману Не тільки новачка, але й досвідченого програміста. Варто чітко розуміти, що компіляторові абсолютно байдуже, як написане оголошення: int *p або int* p. Програміст може вибирати свій стиль. Однак ми будемо зв'язувати символи & і * саме зі змінними, щоб уникнути непорозумінь.
Оператори динамічного розподілу пам'яті
У мові C++ передбачені два оператори динамічного розподілу пам'яті: new і delete. Ці оператори виділяють і звільняють пам'ять у ході виконання програми. Динамічний розподіл пам'яті являє собою важливу частину практично всіх реальних програм. Як вказувалося раніше, у мові C++ є альтернативний спосіб динамічного розподілу пам'яті, заснована на функціях malloc() і free(). Вони збережені для того, щоб досягти сумісності з мовою С. Однак у програмах мовою C++ варто застосовувати оператори new і delete, оскільки вони мають ряд важливих переваг.
Оператор new виділяє область пам'яті й повертає вказівник на її першу адресу. Оператор delete звільняє пам'ять, виділену раніше за допомогою оператора new. Загальний вид цих операторів такий.
вказівник = new тип;
38/54
delete вказівник;
Тут вказівнику приcвoюється адреса першого осередку виділеної області пам'яті, розмір якого визначається типом.
Оскільки обсяг кучі (heap) обмежений, пам'ять може виявитися вичерпаною. У цьому випадку оператор new згенерує виняткову ситуацію bad_alloc, оголошену в заголовку <new>. Програма повинна перехопити цю ситуацію й обробити її. Якщо в програмі не передбачена обробка виняткової ситуації bad_alloc, її виконання буде перервано.
Дія оператора new у виняткових ситуаціях визначено стандартом мови C++. Проблема полягає в тім, що не всі компілятори повністю відповідають стандарту. У ранніх версіях мови C++ оператор new у випадку відмови повертав нульовий вказівник. Пізніше він став генерувати виняткову ситуацію. У підсумку було вирішено, що за замовчуванням оператор new повинен генерувати винятково ситуацію, однак повертати нульовий вказівник також не забороняється. Отже, у кожному конкретному випадку поводження оператора new регламентується розроблювачами компілятора. Зрозуміло, зрештою всі компілятори будуть змушена дотримуватися стандарту, але поки варто орієнтуватися на їхню документацію.
Розглянемо приклад, у якому виділяється динамічна пам'ять для цілочисельної змінної.
39/54
#include <iostream> #include <new>
using namespace std; int main() {
int *p; try {
p |
= new |
int ; // Виділити пам'ять для цілочисельної змінної |
} |
catch |
(bad_alloc xa) { |
cout << "Виняткова ситуація \n"; return l;
}
*р = 100;
cout << "За адресою " << р << " ";
cout << "записане значення " << *р << "\n"; delete p;
return 0;
}
Ця програма присвоює змінній р адресу динамічної пам'яті, у якій може зберігатися цілочисельна змінна. Потім у цю область пам'яті записується число 100, а її вміст виводиться на екран. На закінчення програма звільняє виділену пам'ять. врахуйте, якщо ваш компілятор реалізує оператор new так, що у
40/54
