Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

osn_progr_final

.pdf
Скачиваний:
37
Добавлен:
12.02.2016
Размер:
3.27 Mб
Скачать

{ char_s s1(100); char_s s2(200); s1.push(’a’); s2.push(s1.pop()); char ch=s2.pop(); cout<<chr(ch)<<“\n”;}

При виклиці функції f() конструктор для char_s викликається двічі: для s1 щоб виділити вектор з 100 символів і для s2 для виділення поля з 200 символів. При виході з f() ці два вектора звільняються автоматично.

Розглянемо ще один приклад на використання конструкторів та деструкторів. Нехай маємо такий клас Timer :

class Timer { private: long dt; char*dts;

void DeleteDts(void); publiс:

Timer();

Timer(int m,int d=-1,int y=-1,int hr=-1,int min=-1);

~Timer(); //деструктор

void SetTime(int m=-1,int d=-1,int y=-1,\ int hr=-1,int min=-1);

const char*GetsTime(void); void ChangeTime(long nminutes) {dt+=(nminutes*60); DeleteDts();}

char * GetsTime(void) { if(dts) return dts;

dts = strdup(ctime(&dt)); return dts;}};

Timer ::Timer() {dts=NULL;

Settime(-1,-1,-1,-1,-1);}

Timer ::Timer(int m,int d,int y,int m,int min) {dts=NULL;

SetTime(m,d,y,hr,min);} Timer ::~Timer()

{DeleteDts;}

Маємо фрагмент програми: void f(void); main()

{f();

241

return 0;} void f(void) {Timer today; const char *sp;

sp=today.GetsTime(); cout<<“ Second time :“<<sp;}

В прикладі функція main() викликає функцію f(), всередині якої оголошується автоматичний об’єкт today класу Timer, локальний в області видимості функції. Оскільки локальний автоматичний обєкт створюється разом з викликом функції, в якій він оголошений, С++ викликає для today конструктор Timer в якості частини коду, що виконується функцією при запуску. Подібна операція виконується завжди при виклиці функції. Оскільки вказівник dts посилається на виділений блок пам’яті, то його потрібно звільнити при виході об’єкта з області видимості. Це і робить деструктор, який викликається автоматично. На відміну від конструктора, деструктор може бути викли-

каний явно: tuday.~Timer();

8.6.3 Конструктор копіювання.

Конструктор копiювання є конструктором особливого виду: вiн приймає в якостi параметра константне посилання на об`єкт класу ( const тип_класу &) чи просте посилання на об`єкт( тип-класу &). Приклад:

class Card {int x,y; public:

Card (const Card & src );}; Card :: Card( const Card & src ) {x=src.x;

y=src.y;}

Розглянемо , де бувають корисними конструктори копіювання. Нехай маємо таку програму:

# define LARGE 30000 struct Large

{int data[LARGE]; Large (void)

{for (int i=0; i<LARGE; i++) data[i]=i;}

~Large (void) {} Large slow(Large b) {Large ans;

{ for (int i=0; i<LARGE; i++)

242

ans.data[i]=b.data[i]; return ans;}};

main () {Large a,b;

{for (int i=0; i<100; i++) a.slow(b);};

Час її роботи є порівняно великим. Як збільшити швидкодію ? Це можна зробити з використанням конструктора копіювання:

class Large1 {private:

Large1 (const Large1 & b); public:

int data[LARGE]; Large1 (void)

{for (int i=0; i<LARGE; i++) data[i]=i;}

~LARGE1 (void) {}

Large1 fast(const Large1 &); };

Large1 :: Large1 (const Large1 & b);

{for (int i=0; i<LARGE; i++) data[i]=b.data[i];}

inline Large1 Large1::fast (const Large1 & b)

{ return Large1(b);} main ()

{Large1 a,b;

for (int i=0; i<100; i++) a.fast(b);};

Дана версія програми, що використовує конструктор копіювання, працює значно швидше. Адже за рахунок конструктора копiювання не створюються локальні копiї структур . Конструктор в закритiй частинi опису класу використовується для прискорення оператора return в fast. Це можна пояснити тим, що дані , розміщені в закритому розділі класу, в даному випадку містяться в одному сегменті і доступ до них здійснюється з використанням near-адресації, тобто одного лише зміщення.

ЗАВДАННЯ

1.Дописати в усіх створених в попередніх завданнях класах відповідні конструктори та деструктори.

2.Придумати та протестувати приклади виклику конструкторів для ситуації, аналогічної прикладу з класом str для інших типів даних.

3.Написати програму для ілюстрації автоматичного виклику дестру-

243

кторів . Структури програми можна вибрати, наприклад, такі:а)class pr{

//...

public:

~pr()

{cout<<"Деструктор \n";}}; void main(void)

{ pr

p1;

{pr

p2;

{pr

p3;

cout<<"\n Pa-Pa 3";} cout<<"\n Pa-Pa2";} cout<<"\n Pa-Pa1";}

б)

class pr{//...}

 

class pr1{//..}

 

. . .

 

void f()

 

{ pr a; ...}

 

void f1()

 

{pr1 b;

 

...

 

f(); }

...

main()

{prn b; f(); }

В деструкторах класів видаються відповідні повідомлення.

4.Придумати корисний тип даних, який не є вбудованим в жодній з мов програмування та реалізувати його як абстрактний тип даних С++. Описати відповідні конструктори та деструктори. Написати програму, що використовує цей тип даних.

5.Модифікувати попередній приклад, задаючи певні закони переходу з одного стану в інший.

8.7 ГЛОБАЛЬНІ ТА ЛОКАЛЬНІ ОБ’ЄКТИ.

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

ГЛОБАЛЬНІ ОБЄКТИ.

Конструктори глобальних об’єктів викликаються до початку виконання функції main() в порядку їх слідування. Це забезпечує ініціа-

244

Приклад:

лізацію всіх глобальних об’єктів класу до формального початку виконання програми - виклику функції main(). Деструктри глобальних об’єктів класу викликаються як звичайно в якості частини коду завершення програми, після відпрацювання всіх функцій завершення, заданих викликом функції atexit(). Деструктори не викликаються, якщо програма завершуєтьсмя за допомогою функції abort().

ЛОКАЛЬНІ ОБЄКТИ .

Автоматичні об’єкти класу, оголошені локальними в функції, створюються разом з викликом функції та знищуються разом з завершенням її роботи. Автоматичні об’єкти зберігаються в стеці, як і звичайні автоматичні змінні. При завершенні роботи функції викликаються деструктори локальних об’єктів. Якщо програма завершується за допомогою виклику функції exit(), деструктори глобальних об’єктів викликаються як звичайно, а деструктори локальних об’єктів не викликаються.

class empty{ public:

char *obj_name; empty(char* name)

{cout<<“створений об’єкт з ім’ям”<<name<<“класу empty\n”; obj_name=name;}

~empty()

{ cout<<“об’єкт з ім’ям”<<obj_name<<“знищений\n”;} } one(“one”);

void main(void)

{empty second(“second”);}

Результати роботи програми:

створений об’єкт з ім’ям one класу empty створений об’єкт з ім’ям second класу empty об’єкт з ім’ям second знищений

об’єкт з ім’ям one знищений

ВКАЗІВНИКИ НА ОБЄКТИ

Вказівники можуть посилатись на динамічні об’єкти , пам’ять для яких виділяється за допомогою операції new:

Timer *ptoday; ptoday=new Timer;

В кучі виділяється область для об’єкту типу Timer. Адреса першого байту цієї області присвоюється ptoday. Крім того, С++ викликає конструктор по замовчуванню, який ініціалізує об’єкт. Якщо необхідно виділити пам’ять та викликати інший конструктор, необхідно вказати відповідні параметри:

ptoday=new Timer(9,14,1917,10,45).

245

ОБЄКТИ-ПОСИЛАННЯ

Об’єкти можуть оголошуватись як посилання. Нехай, наприклад, оголошений такий глобальний об’єкт :

Timer today;

Тоді можемо визначити посилання

Timer & rtoday=today;

rtoday-це посилання. Оскільки посилання посилається на вже існуючі об’єкти, при вході в область дії посилання конструктор класу не викликається і, відповідно, не викликається деструктор при виході з області дії.

ОБЄКТИ-РЕЗУЛЬТАТИ ФУНКЦІЙ.

Очевидно, що об’єкти , посилання на об’єкти та вказівники на об’єкти можуть виступати в якості аргументів функцій. Функція може і вертати об’єкт безпосередньо:

Timer newtime(void) {Timer t;

return t;}

Тоді ця функція може викликатись так:

Timer anothertime=newtime();

При цьому результат , який вертається функцією newtime() копіюється в anothertime. Правда, в нашому випадку це не логічно. Адже аналогічні дій виконуються при оголошенні : Timer anothertime; Часто використовують посилання на об’єкт чи вказівник на об’єкт в якості результату функції:

Timer *newtime(void) {Timer *p=new Timer; return p;}

Тоді можна використати newtime() для отримання нового об’єкта та присвоїти його адресу вказівнику на Timer:

Timer*tp=newtime();

Функції-посилання можна використовувати для посилання на існуючі об’єкти:

Timer today;//глобальний об’єкт

Timer &newtimer(void) {return today;}

Тоді можемо оголосити посилання на today так:

Timer & tr=newtimer();

Звичайно, описані вище елементарні приклади є синтаксичною ілюстрацією.

МАСИВИ ОБЄКТІВ .

Масив об’єктів синтаксично задається як звичайний масив:

246

Timer tentimes[10];

При цьому клас, екземпляри якого утворюють масив, повинен обов’язково мати конструктор по замовчуванню. Розглянемо приклад роботи з масивами об’єктів:

main(void)

{ Timer tentimes[10];

for(int i=0;i<10;i++) tentimes[i].display(); return 0;}

Очевидно, що тут буде надруковано 10 однакових значень. Можна ініціалізувати масив , наприклад, так:

main(void)

{Timer tarray[3]={ Timer(),Timer(8),Timer(8,1)}; for(int i=0;i<3;i++) tarray[i].display(); return 0;}

Масиви об’єктів можуть розміщуватись в кучі та адресуватись за допомогою вказівників:

main()

{Timer* tarrayp; tarrayp=new Timer[6]; for(int i=0;i<6;i++) tarrayp[i].display(); delete[ ] tarrayp; return 0;}

Відмітимо, що в цьому прикладі 6 разів викликається конструктор по замовчуванню при виділенні пам’яті оператором new та 6 разів викликається деструктор при звертанні до delete, яка вжита в специфічній формі - з квадратними дужками (C++ ігнорує будь-які значення всередині цих дужок)

ЗАВДАННЯ

1. Описати масив об’єктів (що мають деструктори з відповідними повідомленнями) та проілюструвати роботу оператора delete:

delete a; delete [ ] a; delete &a[3];

delete &a[(int)(sizeof(a)/sizeof(a[0]))];

247

2.Написати функцію ret(), що має параметром індекс масиву об’єктів та повертає посилання на відповідний елемент.

3.Використовуючи функцію ret(), написати програму сортування масиву об’єктів за значеннями відповідних полів.

4.Модифікувати програми завдання 4.3( а)-б) ) так, щоб вони закінчувалася викликом функції exit(). Прослідкувати, як при цьому викликаються деструктори.

5.Розглянути ситуацію, коли програми попереднього завдання закінчується викликом функції abort().

8.8 СТАТИЧНА ПАМ’ЯТЬ ТА КЛАСИ.

При вивченні класів пам’яті ми вже зустрічалися з класом пам’яті static. Специфікатор класу пам’яті static впливає на час життя та область дії змінної чи функції. Змінна , оголошена з специфікатором класу пам’яті static , існує в єдиному екземплярi в межах області видимості.

Клас пам’яті static може використовуватись і при оголошенні полів класу. Специфіка статичних полів полягає в тому, що вони мають одне i те ж значення у всiх об'єктах даного класу. Іншими словами , якщо описати статичне поле при заданні класу, то у всіх екземплярів цього класу значення відповідного поля завжди буде однаковим. Зміна значення статичного поля в одному з об’єктів класу приведе до зміни цього значення у всіх екземплярах класу (для всіх екземплярів класу виділяється одне і те ж саме місце в пам’яті для збереження статичного поля).

class statpol { public:

static int i;} a,b,c; main()

{ a.i=1; cout<<b.i<<“\n”; cout<<c.i<<“\n”;}

Для роботи з статичним полем в С++ версії 3.11 обов’язково необхідно оголошувати статичне поле за межами протоколу класу. При компіляції наведеного вище прикладу помилки не виникне, але виникне помилка на етапі лінкування програми. Щоб цієї помилки уникнути, необхідне зовнішнє оголошення виду:

int statpol::i;

Статичні поля можуть ініціалізуватись за допомогою глобальних оголошень типу:

248

int statpol::i=1;

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

main()

{

statpol a; a.i=1; statpol::i=2; //...}

Допускаються статичнi екземпляри класу. Доступ до полів даних та функцій-членів статичних екземплярів класу ззовнi функцiйчленiв цього класу здiйснюється за допомогою операцiї розширення області видимості :: , що застосовується до імені класу або за допомогою “. “чи “-->“ ( як і для звичайних полiв).

Приклад :

static statpol a; main()

{ cout<<a.i<<“\n”; cout<<statpol::i; }

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

Приклад:

Class Point

{floаt x,y;

static Point *first=NULL; static Point *last=NULL; Point*next;

publiс:

Point (float ax, float ay) {x=ax; y=ay;

if (first==NULL) {first=this; last=this} else {last.next=this; last=this;}}

vоid draw ()

{...}

static void drawAll() {point *p=first;

if (p==NULL) return;

do p->draw() while (p=p->next!=NULL);}};

В прикладі всі екземпляри класу, що породжуються, зберігаються в

249

списку, який потім використовується для малювання всіх створених на даний момент фігур:

if (point :: first!=NULL) point :: drawAll()

Iснують чiткi правила iнiцiалiзацiї полiв всерединi опису класу. Звичайне поле не може бути iнiцiалiзоване при описаннi класу . Iніціалізація статичних полів допускається. Статичнi змiннi можуть бути iнiцiалiзованi за межами класу.

Якщо екземпляр класу оголошений як статичний, то цей клас не може мати конструкторiв, i вiдповiдно нi одне з його полiв не може мати тип іншого класу, який має конструктор.

Приклад:

class A {private: int large;

static int small;}; class B

{private: int bigwant;

static int count; public:

B(void)

{bigwant=25;}}; class C

{private: B b;}

class D {private:

static int size; static A an A; static B a B;

static C a C;//помилка public:

D(void) { }};

Відмітимо, що у випадку якщо оголошений статичний елемент або статичне поле в розділі public, то доступ можливий i через “.”.

Статичні функцiї-члени можуть виконувати будь-які оператори, однак вони не отримують вказівник this та не мають доступу ні до яких полів даних та функцій-членів класу (за винятком статичних). Для виклику статичної функції може використовуватись як об’єкт так і ім’я класу:

Tanyclass::staticfunction();

250

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]