ТА_Методички / Lec_13
.pdfc1(int j) { i=j; }
}; |
|
int main |
|
cl ob(l); |
|
int *p; |
// Одержати адресу члена ob.i. |
р = &ob.i; |
|
cout << *р; |
// Звертання до члена ob.i через вказівник р |
return 0; } |
|
Оскільки вказівник р посилається на ціле число, він має тип int. У цьому випадку не має значення, що змінна i є членом об'єкта ob.
Перевірка типу вказівників
Працюючи з вказівниками, варто мати на увазі: присвоювати можна лише вказіваники сумісних типів.
int *pi; float *pf;
Наступний оператор невірний.
pi = pf; // Помилка - несумісність типів.
11/54
Зрозуміло, будь-яку несумісність можна перебороти, використовуючи приведення типів однак це порушує принципи строгої перевірки типів.
Вказівник this
При виклику функції-члена їй неявно передається вказівник на об’єкт, що викликається. Цей вказівник називається this. Розглянемо програму, у якій описаний клас pwr, призначений для обчислення степеня деякого числа.
#include <iostream> using namespace std; class pwr {
double b; int e; double val; public:
pwr(double base, int exp); double get_pwr() { return val; } };
pwr::pwr(double base, int exp)
{
b = base; e = exp;
12/54
val = 1; if(exp==0) return;
for( ; exp>0; exp --) val = val * b;
}
int main() {
pwr x(4.0, 2), y(2.5, 1), z(5.7, 0) ;
cout << |
x.get_pwr() << " "; |
cout << |
y.get_pwr() << " "; |
cout << |
z.get_pwr() << "\n"; |
return |
0; |
} |
|
Всередині класу до функції-члена можна звертатися прямо, не використовуючи об’єкти і назву класу. Таким чином, всередині конструктора pwr() оператор
b = base;
означає, що змінній b, що належить об'єкту, який викликається, присвоюється значення змінної base. Однак той же самий оператор можна переписати інакше. this->b = base;
13/54
Вказівник this посилається на об'єкт, що викликає функцію pwr(). Таким чином, вираз this->b посилається на змінну b, що належить поточному об'єкту. Наприклад, якщо функція pwr() викликана об'єктом х (в оголошенні х(4.0, 2)), то вказівник this у попередньому операторі буде посилатися на об'єкт х. Втім, цей оператор можна записати в скороченому виді, не використовуючи вказівник this.
Розглянемо повне визначення конструктора pwr(), написане за допомогою вказівника this.
pwr: :pwr(double base, |
int exp) |
|
{ |
base; |
|
this->b = |
|
|
this->e = |
exp; |
|
this->val |
= 1; |
|
if(exp==0) |
return; |
|
for( ; exp>0; exp --) this->val = this->val * this->b; }
Насправді жоден програміст мовою C++ не стане писати конструктор таким чином, оскільки скорочена форма набагато простіша. Однак вказівник this дуже важливий при перевантаженні операторів, а також у ситуаціях, коли функція-член повинна використовувати вказівник на об'єкт, що викликається.
14/54
Слід пам’ятати, що вказівник this автоматично передається всім функціям-членам. Отже, функцію get_pwr() можна переписати інакше.
double get_pwr() { return this->val; }
У цьому випадку, якщо функція get_pwr() викликається за допомогою оператора
y.get_pwr();,
вказівник this буде посилатися на об'єкт y.
На закінчення зробимо два важливих зауваження.
По-перше, дружні функції не є функціями-членами, і, отже, їм не передається вказівник this.
По-друге, статичні функції-члени також не одержують вказівник this.
Вказівники на довільні типи
Як правило, вказівник, одного типу не може посилатися на об'єкт іншого типу. Однак у цього правила є важливе виключення, що стосується похідних класів. Припустимо для початку, що в програмі оголошені два класи: B і D. Крім
15/54
того, припустимо, що клас D є похідним від базового класу B. У цьому випадку вказівник типу B* може посилатися й на об'єкти типу D. У принципі, вказівник на базовий клас можна використовувати як вказівник на об'єкт будь-якого похідного класу.
Однак зворотне твердження невірне. Вказівник типу D* не може посилатися на об'єкти класу B. Крім того, за допомогою вказівника на базовий клас можна посилатися тільки на наслідувані члени, але не на нові члени похідного класу. (Однак вказівник на базовий клас можна привести до типу вказівника на похідний клас і одержати доступ до всіх членів похідного класу.)
Розглянемо коротку програму, що ілюструє застосування вказівника на базовий клас для доступу до об'єктів похідного класу.
include <iostream> using namespace std; class base
{
int i ; public:
void set_i(int num) { i=num; } int get_i() { return i; }
};
class derived: public base {
16/54
int j ; public:
void set_j(int num) { j=num; } int get_j() { return j; }
};
int main()
{
base *bp; derived d;
bp = &d; |
// Базовий вказівник посилається на об'єкт |
//похідного класу.
//Доступ до об'єкта похідного класу за допомогою
//вказівника на похідний клас.
bp->set_i(10);
cout << bp->get_i() << " ";
/* Наступний оператор не працює. На елементи похідного класу не можна посилатися за допомогою вказівника на базовий клас.
bp->set_j(88); |
// Помилка |
cout << bp->get_j(); |
// Помилка |
*/ |
|
return 0; |
|
} |
|
17/54
Як бачимо, вказівник на базовий клас дозволяє звертатися до об'єкта довільного класу.
Вище ми вже відзначали, що вказівник на базовий клас можна привести до типу вказівника на похідний клас. Проілюструємо цю можливість наступним прикладом.
// Приведення типу відкриває доступ
((derived *)bp) -> set_j(88);
cout << ((derived *)bp) -> get_j();
Варто пам'ятати, що адресна арифметика залежить від типу базового вказівника. Тому, якщо вказівник на об'єкти базового класу використовується для доступу до похідних об'єктів, збільшення вказівників не відкриє доступ до наступного об’єкту похідного типу. Замість цього він буде посилатися на об'єкт базового типу. Зрозуміло, це може викликати непорозуміння. Розглянемо програму, що, будучи зовсім правильною із синтаксичної точки зору, є помилковою.
// Ця програма містить помилку.
#include <iostream> using namespace std; class base {
int i;
18/54
public:
void set_i(int num) { i=num; } int get_i() { return i; }
};
class derived: public base { int j;
public:
void set_j(int num) {j=num;} int get_j() {return j;}
}; |
|
|
int main() |
|
|
{ |
|
|
base *bp; |
|
|
derived d[2]; |
|
|
bp = d; |
|
|
d[0].set_i(l); |
|
|
d[l].set_i(2); |
<< |
" "; |
cout << bp->get_i() |
||
bp++; |
// |
Цей оператор стосується до базового, |
cout << bp->get_i(); |
// |
а не похідному типу |
// |
На екран виводиться сміття. |
|
return 0; |
|
|
}; |
|
|
19/54
Використання вказівників на похідні типи виявляється корисним для підтримки динамічного поліморфізму за допомогою механізму віртуальних функцій.
Вказівники на члени класу
У мові C++ існує особливий тип вказівника, що посилається на член класу взагалі, а не на конкретний екземпляр цього члена в об'єкті. Вказівник такого виду називається вказівником на член класу. Цей незвичайний вказівник задає зсув всередині об'єкта відповідного класу. Оскільки вказівники на члени класу не є вказівниками у звичайному розумінні слова, до них не можна застосовувати оператори "." і "->". Щоб звернутися до члена класу за допомогою вказівника на нього, треба застосовувати особливі оператори: ".*" і "->*".
Розглянемо приклад.
#include <iostream> using namespace std; class cl {
public:
cl(int i) { val=i; } int val;
int double_val() { return val+val; }
20/54
