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

osn_progr_final

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

оголошені наступним чином: extern “C”

{//визначення чи прототипи С-функцій}

В цьому випадку компілятор не буде кодувати імена функцій на С, які поміщені в фігурних дужках.

Зручно робити наступним чином: extern C {#include<stdio.h>}

Це призведе до того, що всі прототипи функцій в stdio.h кодуватись не будуть. Програма на С++ в цьому випадку може використовувати одне чи більше імен функцій з <stdio.h> не боячись недопустимого перевантаження.

8.4.12Про структури та об’єднання.

ВС++ структури та об’єднання мають ряд нових властивостей. На відміну від ANSI C, полями структур та об’єднань можуть бути функції. Крім цього, структури та об’єднання мають інші властивості, які є аналогічними і для класів. (В літературі інколи класи, структури та об’єднання навіть розглядають разом, див.[5]). Структури та класи відрізняються лише тим, що члени класу по замовчуванню мають режим доступу private, а члени структури - public. Деякі відмінності мають об’єднання. В об’єднаннях не допускаються ключові слова private, public та protected . Об’єднання не можуть брати участь

вмеханізмі наслідування. В С++ допускаються неіменовані об’єднання:

union{список членів};

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

Приклад:

main()

{ union{long l; unsigned char ch[4]};

cout<<“введіть число типу long:”; cin>>l;

cout<<”молодший байт”<<(int)ch[0]<<“\n”; cout<<”другий байт”<<(int)ch[1]<<“\n”; cout<<”третій байт”<<(int)ch[2]<<“\n”; cout<<”старший байт”<<(int)ch[3]<<“\n”;

231

return 0;}

ЗАВДАННЯ

1.Написати систему перевантажених функцій сортування масивів елементів різних типів.

2.Написати функцію знаходження максимального значення довільної кількості параметрів (<=20), вказаних як аргументи функції(типу max(a1,a2,...,an)).

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

4.Перевірити приклад на використання директиви inline для вашої версії компілятора С++ та комп’ютера з використанням бібліотечної функції Gettime() чи інших аналогічних функцій.

5.Описати структуру, полями якої є деякі вказівники. В головній програмі визначити посилання на елемент відповідної структури. Перевірити, що відбувається у випадку звілнення пам’яті відповідного поля за допомогою оператора delete.

6.Визначити динамічний масив цілих чисел a та перевірити роботу оператора delete:

delete a; delete [5]a; delete &a[3]; delete a+3; delete &a[sizeof(a)];

7.Написати систему перевантажених функцій malloc, які виділяють пам’ять розміром n*p байт та повертають вказівник на тип першого аргумента, де n - ціле значення другого аргумента, p -розмір в байтах першого аргумента .

8.5 ФУНКЦІЇ-ЧЛЕНИ КЛАСУ.

8.5.1 Функції-члени в межах та за межами формального опису класу.

Як вже відмічалось, тіло функції-члена може бути визначене як в межах формального опису класу так і за його межами. В першому випадку компілятор розглядає функцію як inline-функцію. Можна явно визначати inline-функцiю за допомогою вiдповiдної директиви:

class x

{int m; public :

int readm (void);}; inline int x :: readm (void)

{return m;}

В цьому прикладі код функції readm буде вставлятись безпосередньо в місце її виклику. Очевидно, що це приведе до зростання швид-

232

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

class x

{ int m; public :

int readm (void) {return m;} };

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

Відмітимо, що компілятор з високим рівнем оптимізації може у випадках , коли тіло функції містить багато операторів, не вставляти код програми в місце її виклику, якщо навіть присутній специфікатор inline

8.5.2 Про вказівник this.

Як вже відмічалось, в тiлі функцiї-члена оператори мають безпосереднiй доступ до будь-яких полів даних та функцій-членiв, визначених в протоколі.Часто в протоколі опису класу виникає необхідність викликати функцію-член всередині іншої функції-члена. Якщо послідовно дотримуватись об’єктно-орієнтованої парадигми, то вкладений виклик функції повинен інтерпретуватись як передача повідомлення об’єкту. Об’єкт, що приймає вкладене повідомлення - це той самий об’єкт, який приймає повідомлення верхнього рівня. Тобто об’єкт повинен “вміти” передавати повідомлення сам собі. Як вийти з цієї ситуації так, щоб повністю підтримувалась концепція ООП ? І вирішена ця проблема наступним чином. В С++ будь-яка функціячлен класу має додатковий скритий формальний параметр - вказівник на клас, для екземпляру якого викликається функція-член. Причому він завжди ініціалізується системою С++ так, що вказує на об’єкт, для якого викликається функція-член. Іншими словами, будьяка функція-член класу Х в сигнатурі містить неявне оголошення виду:

X *this;

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

233

такий клас:

class Х{

int m ; public :

int readm (void){ return m;}};

Функцію-член readm () можна записати і так:

int readm (void){ return this->m;}

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

class dlink {

dlink * pre dlink * suc; public:

void append (dlink*);}; void dlink ::append (dlink*p); {p->suc=suc; // тобто p->suc=this->suc; p->pre=this;

suc->pre=p; //тобто this->suc->pre=p suc=p;}

В такій реалізації функція append() вставляє елемент, вказівник на який передається як параметр в функцію, після елемента, який викликав функцію -член append().

8.5.3 Перевантаження функцій-членів. Параметри по замовчуванню.

Функції - члени класу можна перевантажувати. Механізм перевантаження функцій-членів класу аналогічний механізму перевантаження “звичайних” функцій. За допомогою перевантажених функцій можна створювати надзвичайно гнучке програмне забезпечення. Адже ми можемо передбачити реалізації якоїсь функції “на всі випадки життя”. Розглянемо, наприклад, вже згаданий раніше клас Timer. Очевидно, що нам знадобляться якісь функції встановлення дати та часу. Проте, можна уявити собі ситуацію, коли нас цікавить лише час, а дата не цікавить взагалі або навпаки. Тому можемо написати кілька функцій з різними сигнатурами та діями, передбачивши різні ситуації:

class Timer {

234

private: int year; int month; int day; int hour; int minute; public:

void Display (void);

void SeTime(int phour, int pminute); void SeTime(int ,int , int );};

void Timer::Display (void)

{printf ("year=%d month=%d day=%d hour=%d \ minute=%d \n", p-> year, p-> month, p-> day,\

p-> hour ,p-> minute);}

void Timer::SetTime(int pyear,int pmonth, int pday) {year=pyear;

month=pmonth;

day=pday;}

void Timer::SetTime(int phour, int pminute) {hour=phour;

minute=pminute; }

Перевантаження функцій є єфективним механізмом у випадках, коли реалізації перевантажених функцій принципово різні. В нашому ж прикладі можна легко обійтись без перевантаження функцій, використовуючи параметри по замовчуванню. Дійсно, писати свою функцію для кожного випадку ініціалізації полів даних класу Timer було б дуже нераціонально. Теоретично можлива кількість таких функцій, якщо підійти до цього питання чисто формально - 32 . В рамках правил перевантаження функцій дупускається ж лише 5. Якщо б ми захотіли змоделювати всі ці ситуації за допомогою механізму параметрів по замовчуванню, то теоретично необхідно було б 5!=120 (для кожного розміщення параметрів -по одній) функцій-членів класу виду:

void Timer::SetTime (int pyear=0,int pmonth=0;\ int pday=0,int phour=0,int pminute=0;)

{year=pyear;

month=pmonth;

day=pday;

hour=pour;

minute=pminute;}

Це випливає з правил передачі параметрів по замовчуванню. Проте компілятор “пропустить” лише одну таку функцію. Очевидно, що всі

235

6 допустимих варіантів перевантажених функцій з прототипами

SetTime(), SetTime(int), SetTime(int,int), SetTime(int,int,int),

SetTime(int,int,int,int),SetTime(int,int,int,int,int) мо-

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

ЗАВДАННЯ

1.Написати функції вставки та видалення елемента в а) однозв’язному списку б) двозв’язному списку в) бінарному дереві

Використати вказівник this.

2.Утворити клас з перевантаженими функціями-членами розв’язку СЛАР різними методами

3.Описати клас Draw , який містить кілька функцій, що малюють різні геометричні фігури. Використати його в програмі.

4.Описати клас Complex , який містить набір функцій, що виконують різні дії з комплексними числами.

5.Описати клас Орієнтований граф з набором методів роботи з ним.

6.Змінити реалізацію класу Complex так, щоб можна було використовувати полярні координати замість пар дійсна-уявна частина. Подумайте, як вплинуть ці зміни на код користувача.

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

8.Реалізувати тип хеш-таблиці з попереднього завдання з використаннямструктури бінарного дерева без зміни функцій вставки, перекодування та видалення.

236

8.6 КОНСТРУКТОРИ ТА ДЕСТРУКТОРИ. 8.6.1 Поняття про конструктори.

Розглянемо описаний вище клас Timer. Оголосимо екземпляр класу v:

Timer v;

При такому оголошеннi відбувається лише виділення пам’яті для відповідного екземпляра класу. Програміст повинен сам подбати про ініціалізацію полів. Якщо уявити собі наявність кількох класів, що мають складну структуру, велику кількість полів даних, то задача акуратної та правильної ініціалізації полів є досить громіздкою та помилконебезпечною. Адже в такому випадку необхідно тримати в пам’яті всю структуру цих класів. В деяких випадках при створенні об’єкту необхідним є і виділення пам’яті. У зв’язку з цим в С++ введені функції-члени, що мають особливий синтаксис , завдання та назву. Це конструктори. Вони служать для ініціалізації полів даних, виділення пам’яті, якщо це необхідно, та виконання інших дій, які можуть бути потрібними при створенні екземпляра об’єкту. Саме слово конструктор - від англійського слова constuct -будувати, конструювати.

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

Приклад: clas TT {

private: long dt; char*dts; publiс:

TT(int, int);

237

};

TT ::TT(int a, int n) {dt=a;

dts=(char*)malloc(n*sizeof(char));}

main()

{

TTt2(1,15);

//. . .}

При оголошенні TT t2(1,15); зразу буде викликаний конструктор класу, який ініціалізує поле dt та виділяє пам’ять. Умовою його виклику є відповідність параметрів, вказаних в дужках, сигнатурі конструктора. Якщо в нашому прикладі оголосити змінну t2 так: TT t2(1,1); , то буде видане повідомлення про помилку. Адже конструктора з відповідною сигнатурою не існує.

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

Приклад:

class TT { private:

long dt; char*dts;

publiс:

TT();}; TT ::TT(void)

{

dt=0;

dts=(char*)malloc(10*sizeof(char));}

main()

{

TTv;

//. . . .}

Вмомент оголошення змінної-екземпляра класу v відбувається виклик конструктора по замовчуванню. Розглянемо ще один приклад. Опишемо такий клас:

class example

{

238

public: int i;

example (int i) {i=_i;} };

і визначимо екземпляр класу : example a;

Тут виникне помилка. Адже в класі example немає void-конструктора. В такій ситуації необхідно визначити цей конструктор, хоча б з формально порожнім тілом:

class example

{

public: int i;

example (int i) {i=_i;} example (){}

};

Відмітимо, що відповідні конструктори класів викликаються і в ситуаціях, коли необхідним є неявне перетворення типів (див. п.12.2).

Якщо конструктор вiдсутнiй, то такий об'єкт можна iнiцiалiзувати шляхом присвоєння йому об'єкта цього ж класу.

Timer today;

...

Timer d=today;

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

class IntArray {private:

int *data; unsigned size;

public:

IntArray(void); IntArray(unsigned mySize);

void put(unsigned index, int value); int get(unsigned index);};

int IntArray::get(unsigned index) {if (index<size) return data[index]; else return 0;}

void IntArray:: put(unsigned index, int value) { if (index<size) data[index]=value; } IntArray:: IntArray(void)

{data=new int[100]; size=100;

239

for(int i=0;i<size;i++) data[i]=0;} IntArray IntArray(unsigned mySize) {data=new int[mySize]; size=mySize;

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

main()

{IntArray myArray(200); IntArray second(200); second=myArray; myArray.put(10,-50); cout<<second.get(10);}

В результаті буде надруковане значення 10-того елемента масиву -50. Тобто для даних об’єктів виділяється одна і та ж область пам’яті. При присвоюванні об”єктів не відбувається виклик конструкторів. Цю проблему можна легко вирішити, якщо перевантажити оператор присвоювання (див. розділ “Перевантаження операторів” ).

8.6.2 Деструктори.

Оскільки існують функції, які виконують ряд дій по конструюванню об’єкту, виникає необхідність у існуванні протилежних до них за дією функцій. Це особливо зрозуміло, якщо врахувати можливі дії конструкторів по виділенню пам’яті. Такі функції в С++ є і називаються вони деструкторами. Ім’я деструктора завжди починається з символа ~ за яким слідує ідентифікатор імені класу: ~ім’я_класу(); Деструктор не може мати параметрів. В класі може бути оголошений тiльки один деструктор.

Деструктор викликається автоматично при виходi об'єкту з областi видимостi.

class char_s{ int size; char *top; char *s; public:

char_s(int sz) {top=s=new char[size-sz];} ~char_s() {delete s;}

void push(char c) {*top++=c;} char pop() {return *--top;}};

Коли char_stack виходить з області видимості, викликається деструктор:

void f()

240

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