1   2   3   4
Ім'я файлу: prog_lecture_07.pdf
Розширення: pdf
Розмір: 205кб.
Дата: 29.09.2021
скачати
7.1.5.
Статичні
змінні

члени класу
Статичні змінні-члени мають особливі властивості. Вони доступні всім об'єктам класу, не належачи нікому з них окремо. Якби клас не був абстракцією, можна було б сказати, що статична змінна належить класу, а не об'єктам. У реальності це означає, що статична змінна-член класу існує в єдиному екземплярі незалежно від кількості об'єктів. Особливо важно, що статична змінна ініціалізується ще до створення першого об'єкта класу.
Оголошення статичної змінної-члена класу має декілька тонкощів. По-перше, замало оголосити статичну змінна-член — необхідно виділити для неї пам'ять. Для цього її оголошення слід повторити поза тілом класу, в області визначення глобальних змінних. Визначення статичної змінної-члена можна сполучити з її
ініціалізацією. По-друге, повторювати ключове слово static при визначенні статичної функції не можна!
Воно повинно бути присутнім тільки в тілі класу! Розглянемо приклад, у якому змінні
Re
і
Im оголошені
статичними членами класу
TComplex
Оголошення статичних членів класу
#include
#include
class TComplex
{
static double Re;
static double Im;
public:
void print();
void init();
};
double TComplex::Re=0.0;
double TComplex::Im=0.0;
int main()
{
TComplex Z;

Лекція
7
Класи
7
printf("sizeof = %d \n",sizeof(Z));
Z.init(); //
Виклик функції
- члена init()
Z.print(); //
Виклик функції
- члена print()
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}
void TComplex::init() {Re = 1; Im = 2;}
Цікаво, що розмір класу
TComplex змінився. Тепер він дорівнює лише одному байту (як у зовсім порожнього класу).
Статичні змінні зручно використовувати як лічильник об'єктів, а також як індикатори на зразок «Зайнято» —
«Вільно». Об'єкти можуть по черзі змінювати значення статичної змінної, повідомляючи своїм спадкоємцям, що вони виконали свою місію.
Розглянемо програму, у якій створюються і виводяться на екран 25 комплексних чисел. Зверніть увагу на синтаксичну конструкцію, прийняту для звертання до статичної змінної-члена:
TComplex::counter
. Це підкреслює, що статична змінна-член не належить жодному об'єкту. До речі, це не єдина форма звертання до неї. Можна також використовувати вираз
TComplex.counter
. Для того щоб підкреслити, що кожен об'єкт має доступ до статичного змінна-члену, можна оператор
TComplex.counter++
замінити оператором
Z.counter++
Використання статичних членів класу
#include
#include
class TComplex
{
double Re;
double Im;
public:
static int counter;
void print();
void init(double x, double y);
};
int TComplex::counter=0;
int main()
{
for (int i = 1; i <= 5; i++)
for (int j = 1; j <= 5; j++)
{
TComplex Z;
Z.init((double)i,(double)j); //
Виклик функції
- члена init()
Z.print(); //
Виклик функції
- члена print()
TComplex::counter++; //
Інкрементація статичної
// змінна
- члена
}
printf("Counter = %d\n",TComplex::counter);
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}
void TComplex::init(double x, double y) {Re = x; Im = y;}
7.1.7.
Статичні
функції
-
члени класу
Статичними можуть бути не тільки змінні-члени, але і функції-члени класу. У цьому випадку вони втрачають доступ до нестатичних членів класу і можуть звертатися тільки до статичних змінних-членів і функцій. Крім того, вони не можуть бути віртуальними, одержувати вказівник this
, а також не повинні мати кваліфікаторів const
і volatile
Розглянемо програму, у якій для висновку значення статичного лічильника застосовується статична функція
TComplex::HowMuch()
Використання статичних функцій
-
членів класу

Лекція
7
Класи
8
#include
#include
class TComplex
{
double Re;
double Im;
public:
static int counter;
void print();
void init(double x, double y);
static void HowMuch();
};
int TComplex::counter=0;
int main()
{
for(int i = 1; i <= 5 ; i++)
for(int j = 1; j <= 5 ; j++)
{
TComplex Z1;
Z1.init((double)i,(double)j); //
Виклик функції
- члена init()
Z1.print(); //
Виклик функції
- члена print()
TComplex::counter++; //
Інкрементація статичного лічильника
}
TComplex::HowMuch(); //
Виклик статичної
функції
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}
void TComplex::init(double x, double y) {Re = x; Im = y;}
void TComplex::HowMuch(){printf("Counter = %d\n",TComplex::counter);}
Застосування статичних функцій обмежено ініціалізацією статичних змінних, котру необхідно виконати до створення реальних об'єктів. Наприклад, вони лежать в основі корисної ідіоми «іменованих конструкторів», яку ми розглянемо нижче.
7.1.7.
Константні
функції
-
члени
Для захисту від модифікації члени класу, як правило, поміщають у закритий розділ і оголошують константними. Однак, якщо надалі нам знадобиться змінити константне поле, прийдеться виконувати приведення типу за допомогою оператора const_cast
. Утім, існує ще один спосіб захистити поля —
розділивши функції-члени на функції доступу і функції, що модифікують. Перші функції не мають права змінювати вміст полів. Для цього них оголошують константними. Друга група функцій може вільно привласнювати полям нові значення.
Наприклад, у наведеному нижче лістингу функція print()
є константною і не може присвоїти членам
Re
і
Im жодних значень. Для цього між її заголовком і тілом поставлене ключове слово const
. Зверніть увагу на те,
що функція change()
теж оголошена константною. Отже, вона також не повинна змінювати зміст полів класу.
Однак ми зняли захист з одного з членів класу —
Re
— поставивши перед його оголошенням ключове слово mutable
. Тепер будь-яка константна функція зможе змінити значення цього поля.
Застосування ключових слів
const
і
mutable
#include
class TComplex
{
mutable double Re;
double Im;
public:
TComplex():Re(0),Im(0){};
void change(double x) const {Re = x};
void print() const { printf("Z = %lf + i*%lf \n",Re,Im);}
}Z;
int main()
{

Лекція
7
Класи
9
TComplex X;
X.print();
X.change(1.0);
X.print();
return 0;
}
Результат приведений нижче
Z = 0.000000 + i*0.000000
Z = 1.000000 + i*0.000000
7.1.8.
Вкладені
класи
Мова С++ дозволяє створювати вкладенікласи. Це дозволяє сховати внутрішній клас від зовнішнього світу,
обмеживши його область видимості зовнішнім класом.
Проілюструвати ця властивість досить просто. У попередній програмі перенесемо оголошення класів
TDouble
і
TInteger усередину класу
TComplex
Визначення внутрішніх класів
#include
class TComplex
{
class Double
{
double x;
public:
Double(double y):x(y){printf(" Ctor Double \n");}
};
class Integer
{
int n;
public:
Integer(int m):n(m){printf(" Ctor Integer \n");}
};
Double Re;
Integer Im;
public:
void print();
TComplex(Integer k, Double v):Re(v), Im(k){};
};
int main()
{
TComplex Z(10.0, 20);
Z.print();
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%d \n",Re,Im);}
Внутрішній (чи локальний) клас можна визначити не тільки усередині класу, але й усередині функції,
наприклад усередині функції main()
Визначення локального класу
#include
int main()
{
class TComplex
{
double Re;
int Im;
public:
void print(){printf("Z = %lf + i*%d \n",Re,Im);};
TComplex(int k, double v):Re(v), Im(k){};
};

Лекція
7
Класи
10
TComplex Z(10.0, 20);
Z.print();
return 0;
}
Оскільки функції не можна визначати усередині інших функцій, їх визначення необхідно занурити усередину локального класу. Тільки під цією оболонкою вони доступні для зовнішньої функції.
У силу обмеженості області видимості локальних класів, їм недоступні інші локальні змінні, оголошені
усередині функції, крім статичних і зовнішніх. Крім того, усередині локальних класів неможливо визначити статичні змінні.
7.1.9.
Вказівники на членів класу
У деяких ситуаціях можна одержати доступ до функції-члена класу, не вказуючи імен ні об'єкта, ні функції.
Механізм, що дозволяє це зробити, ґрунтується на використанні вказівників на член класу. Зрозуміло, щоб викликати функцію, необхідний реальний об'єкт класу, однак доступ до його функції-члену програма одержує за допомогою непрямої адресації. Для цього спочатку необхідно створити об'єкт у динамічній пам'яті й установити на нього вказівник. Потім з'являється вказівник на функцію-член класу. Тепер, передаючи як параметри вказівники на об'єкти і функції-члени, ми можемо викликати кожну функцію-член класу, не називаючи її явно.
Природно, її оголошення повинне точно відповідати оголошенню вказівника на функцію-член.
Виклик функції
-
члена класу через вказівник на неї
#include
#include
class TComplex
{
double Re;
double Im;
public:
void print(){printf("Z = %lf + i*%lf \n", Re, Im);}
void init(){Re = 1; Im = 1;}
};
typedef void (TComplex::*pMem)(); //
Оголошення вказівника на функцію
- член void call(TComplex* s, pMem p);
int main()
{
TComplex* pObj = (TComplex*)malloc(sizeof(TComplex));
pMem p;
p= &TComplex::init;
call(pObj,p);
p = &TComplex::print;
call(pObj,p);
return 0;
}
void call(TComplex* s, pMem p)
{
(s->*p)();
}
Не слід плутати вказівник на функцію-член з вказівником на звичайну функцію. Вказівник на функцію-член задає не адресу, а зсув члена класу щодо адреси об'єкта.
Вказівники можна встановлювати не тільки на функції-члени, але і на дані-члени. Розглянемо як приклад наступну програму.
Використання вказівників на члени класу
:
перший варіант
#include
#include
class TComplex
{
public:
double Re;
double Im;
};

Лекція
7
Класи
11
double TComplex::*pMem; //
Вказівник на член класу
, що має
тип double void initRe(TComplex*, double TComplex::*pMem);
void initIm(TComplex*, double TComplex::*pMem);
void printRe(TComplex, double TComplex::*pMem);
void printIm(TComplex, double TComplex::*pMem);
int main()
{
TComplex z;
pMem = &TComplex::Re;
initRe(&z,pMem);
pMem = &TComplex::Im;
initIm(&z,pMem);
pMem = &TComplex::Re;
printRe(z,pMem);
pMem = &TComplex::Im;
printIm(z,pMem);
return 0;
}
void initRe(TComplex* p, double TComplex::*pMem)
{
p->*pMem = 1.0;
}
void initIm(TComplex* p, double TComplex::*pMem)
{
p->*pMem = 1.0;
}
void printRe(TComplex z, double TComplex::*pMem)
{
printf(" Re = %lf \n", z.*pMem);
}
void printIm(TComplex z, double TComplex::*pMem)
{
printf(" Im = %lf \n", z.*pMem);
}
Тут продемонстровані два способи звертання до члена класу через вказівник на нього: прямий і непрямий.
Прямий спосіб використовує об'єкт класу: z.*pMem
, а непрямий — посилання на нього: p->*pMem
. В обох випадках виробляється розіменування вказівника на член класу pMem
На жаль, вказівники на члени класу не підкоряються правилам арифметики, прийнятим для звичайних вказівників. Саме з цієї причини нам приходиться ініціалізувати кожен член класу
TComplex окремою функцією — адже в нас немає засобів, що дозволяють переміщатися по членах класу шляхом інкрементації
вказівника pMem
Утім, приведену вище програму можна небагато поліпшити, зробивши вказівник на член класу локальним.
Використання вказівників на члени класу
:
другий варіант
#include
#include
class TComplex
{
public:
double Re;
double Im;
};
void init(TComplex*, double, double);
void print(TComplex);
int main()
{
TComplex z;
init(&z, 1.0, 2.0);

Лекція
7
Класи
12
print(z);
return 0;
}
void init(TComplex* p,double x, double y)
{
double TComplex::*pMem;
pMem = &TComplex::Re;
p->*pMem = x;
pMem = &TComplex::Im;
p->*pMem = y;
}
void print(TComplex z)
{
double TComplex::*pRe, TComplex::*pIm;
pRe = &TComplex::Re;
pIm = &TComplex::Im;
printf(" z = %lf + i* %lf\n", z.*pRe, z.*pIm);
}
Тепер кількість функцій скорочена вдвічі.
7.2.
КОНСТРУКТОРИ
І
ДЕСТРУКТОР
У попередніх прикладах ми ініціалізували об'єкт класу
TComplex за допомогою функції init()
. Утім, це було зайве. У мові С++ для ініціалізації об'єктів призначений механізм, називаний конструктором. Це —
функція-член, ім'я якої збігається з ім'ям класу. Вона може мати будь-які параметри, що необхідні для
ініціалізації полів об'єкта. При цьому конструктор не має ніякого значення, що повертається. Його виклик являє
собою визначення об'єкта. Інакше кажучи, визначаючи об'єкт, ви викликаєте конструктор.
Оголошення об
'
єкта
#include
#include
class TComplex
{
double Re;
double Im;
public:
static int counter;
void print();
TComplex(double x, double y);
static void HowMuch();
};
int TComplex::counter=0;
int main()
{
for(int i = 1; i <= 5 ; i++)
for(int j = 1; j <= 5 ; j++)
{
TComplex Z1(i,j); //
Виклик конструктора
Z1.print(); //
Виклик функції
- члена print()
TComplex::counter++;
}
TComplex::HowMuch();
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}
TComplex::TComplex(double x, double y) {Re = x; Im = y;}
void TComplex::HowMuch(){printf("Counter = %d\n",TComplex::counter);}
Такий спосіб визначення найбільш розповсюджений. Однак існує ще одна форма:
TComplex Z = TComplex(i,j);
Цю конструкцію ми розглянемо пізніше.

Лекція
7
Класи
13
7.2.1.
Конструктори з
одним параметром
Вище ми розглянули варіант конструктора з двома параметрами. Узагалі кажучи, конструктори можуть мати будь-які кількість чи параметрів не мати їх зовсім. Однак якщо конструктор має один параметр, виникає
особлива ситуація.
Припустимо, що ми хочемо створити комплексне число з однаковими дійсною і уявною частинами.
Природно, для його ініціалізації достатньо одного параметра.
Конструктор з
одним параметром
#include
#include
class TComplex
{
double Re;
double Im;
public:
void print();
TComplex(double v);
};
int main()
{
TComplex X(10); //
Перший варіант
X.print();
TComplex Y = TComplex Y(10); //
Другий варіант
TComplex Z = 10; //
Третій варіант return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}
TComplex::TComplex(double v) {Re = v; Im = v;}
Конструктор класу
TComplex має один параметр. Для нього існує три форми ініціалізації; остання дозволяє виконати перед ініціалізацією неявне перетворення типу.
7.2.2.
Список
ініціалізації
Найчастіше зручно задавати початкові значення за допомогою списку ініціалізації. Його синтаксична конструкція виглядає в такий спосіб.
ім
'
я
_
класу
(
тип параметр
1, ... тип параметр
N):
член
_
класу
(
параметр
1), ...,
член
_
класу
(
параметр
N) {}
Список ініціалізації — єдиний спосіб задати значення константних змінних-членів. Крім того, така
ініціалізація буває більш ефективною. Правда, якщо клас містить велику кількість членів, список може стати занадто довгим, що знизить наочність програми.
Використання списку
ініціалізації
#include
class TComplex
{
double Re;
double Im;
public:
void print();
TComplex(double v):Re(v), Im(v){};
};
int main()
{
TComplex Z=10;
Z.print();
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%lf \n",Re,Im);}

Лекція
7
Класи
14
Згадаємо, що клас може містити об'єкти іншого класу. У цьому випадку при ініціалізації елементів списку будуть викликатися відповідні конструктори. Порядок їх виклику визначається списком параметрів (причому в зворотному напрямку), а не списком ініціалізації, хоча правила гарного стилю вимагають не порушувати їхній природний порядок. При цьому порядок перерахування полів у класі значення не має.
Уявимо собі комплексні числа, у яких дійсні частини задаються числами з точкою, що плаває, а уявні —
цілими числами. Будемо вважати, що дійсні і цілі числа імітуються фіктивними класами
Double
і
Integer відповідно.
Порядок перерахування аргументів у
списку
ініціалізації
#include
class TDouble
{
double x;
public:
TDouble(double y):x(y){printf("
Конструктор
TDouble \n");}
};
class TInteger
{
int n;
public:
TInteger(int m):n(m){printf("
Конструктор
TInteger \n");}
};
class TComplex
{
TDouble Re;
TInteger Im;
public:
void print();
TComplex(TDouble v, TInteger k):Re(v), Im(k){};
};
int main()
{
TComplex Z(10.0, 20);
Z.print();
return 0;
}
void TComplex::print() {printf("Z = %lf + i*%d \n",Re,Im);}
Виконання цієї програми супроводжується наступними повідомленнями.
Конструктор
TInteger
Конструктор
TDouble
Z = 10.00000 + i*20
Переставимо параметри в конструкторі
TComplex
:
TComplex(TInteger k, TDouble v):Re(v), Im(k){}
На екрані з'являться такі рядки.
Конструктор
TDouble
Конструктор
TInteger
Z = 10.00000 + i*20

1   2   3   4

скачати

© Усі права захищені
написати до нас