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

osn_progr_final

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

}

class iterator { associ* cs;

int i; public:

iterator (associ& s){cs=&s; i=0;} vidnosh* operator()(void)

{

return(i<cs->free)?&cs->vec[i++]:NULL;

}

};

main()

{

vidnosh* p=new vidnosh; const Max=256;

char buf[Max]; associ mas(512);

while (cin>>buf) mas[buf]++; iterator next(mas);

while (p=next()) cout<< p->name<<" "<<\ p->val<<"\n";

}

В наведеному прикладі клас iterator містить перевантажений оператор виклику функції, який повертає наступний елемент динамічного масиву vec, елементами якого є структури типу vidnosh. Цей масив (в прикладі 512 елементів) утворюється при визначенні екземпляра mas класу assoc та ініціалізується в циклі while (cin<<buf) mas[buf]++; Тіло циклу виконується доти, доки не зустрінеться символ кінця файлу. В останньому операторі while просто друкується вміст масиву: символьний рядок та кількість його входжень у вхідному потоці.

8.12.3.3 Операція доступу до члена класу.

Перевантажити можна операцію доступу до члена класу через вказівник

(->). Перевантажується він так: operator->(). Функція-член operator- >() повинна повертати вказівник на об’єкт класу. Розглянемо такий приклад:

class MyClass { public:

int x,y;

MyClass (int _x,int _y){x=_x;y=_y;}

291

MyClass * operator->(); };

MyClass * MyClass :: operator->()

{

x*=2;

y*=2;

cout<<"\n Перевантажений оператор:"; return this;}

В даному прикладі перевантажена операція подвоює значення полів та видає коротке повідомлення перед тим, як повернути вказівник this. Тоді в основній програмі можемо написати:

main()

{MyClass a(12,25); cout <<a->x<<'\n'; cout <<a->y<<'\n'; return 0;}

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

Наведені в main-функції операції a->x та a->y насправді виконуються так:

(a.operator->())->x<<“\n”; (a.operator->())->y<<“\n”;

8.12.3.4 Перевантаження операцій інкремента та декремента.

В ранніх версіях С++ неможливо відрізнити постфіксні та інфіксні форми при перевантаженні операцій. Починаючи з версії 4.5 це можна зробити, наприклад, так:

class MyClass {int x; public:

MyClass(int _x){ x=_x;}

int operator ++() { return ++ x ;}

int operator ++(int) {return x++;} int operator --(){return --x;} int operator --(int){return x--;} int Getx(void)

{return x;}};

292

Нехай визначений екземпляр

MyClass v;

Тоді будуть еквівалентними наступні конструкції: v++; та operator++(0)(v);

++v; та operator ++() (v); main () {

TanyClass t(100); cout <<t++; // cout <<++t; //}

8.12.3.5 Перевантаження операцій управління пам’яттю

(NEW,DELETE).

Як відомо, в програмах ANSI C використовуються функції malloc() та free() разом з їм подібними для виділення та звільнення пам’яті в кучі. В С++ використовуються операції new та delete. У більшості версій С++ ці операції насправді скрито реалізуються викликами функцій malloc() та free(). Тому може скластись враження, що немає принципової різниці в управлінні пам’яттю в мовах C та С++. Проте це не так. Той факт, що new та delete є операціями дає можливість в С++ їх перевантажити. Це дозволить створювати більш гнучку систему роботи з пам’яттю для об’єктів.

Розглянемо приклад. Приклад 1.

#pragma warn -aus

//виключаються повідомлення компілятора про те, що //визначаються та не використовуються змінні

char buf [512]; int index; class MyNew {

int x; public: MyNew(){};

void* operator new(size_t )

{

cout<<"перевантажений оператор new:\n"; if (index>512-sizeof(MyNew)) { cout<<"недостатньо пам’яті\n";

return 0;} else{

int k=index;

index +=sizeof(MyNew); cout<<"\n"<<index<<"\n";

return &buf[k];}

293

}

}; main ()

{

MyNew *b=new MyNew; MyNew *b1=new MyNew;

}

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

перевантажений оператор new:

4

перевантажений оператор new:

8

Якщо в main()-функції дописати цикл for(int i=0;i<127;i++)

MyNew *b2=new MyNew;

то отримаємо повідомлення : недостатньо пам’яті. Якщо ж оголосити масив елементів типу MyNew

Mynew *mas=new MyNew[20];

то буде викликатись стандартна операція new.

Відмітимо, що в С++ версії 4.5 вимагається наявність параметра типу size_t в перевантаженому операторі new. Тому в наведеному вище прикладі записується параметр size_t в оголошенні:

void* operator new(size_t);

Аналогiчно перевантажується оператор delete. Прототип функції перевантаження оператора delete повинен мати вигляд: void operator delete(void* p); де р посилається на об’єкт, що знищується. В якості альтернативи можемо оголосити функцію, наприклад, так:

void operator delete(void*p, int size);

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

8.12.3.6 Перевантаження операції присвоювання.

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

class intarray{ int size; int *ar;

public: intarray(int size) { ar=new int[size];}

void operator=(const intarray & anintarray) { ar=new int[anintarray.size];

for(int i=0;i<size;i++)

294

ar[i]=anintarray.ar[i]; } };

Тоді в main() - функції можемо написати: main()

{ intarray a(100); intarray b=a;}

ЗАВДАННЯ :

1.Перевантаживши операції < та > , написати функцію сортування масиву об’єктів за деякими ознаками його полів :

а) в порядку зростання(спадання) значень одного з полів типу char, int ,float чи double ;

б) в порядку лексикографічної впорядкованості полів типу *char;

в) вважаючи, що клас має два поля p1 та p2 типу double, ввести відношення часткового порядку наступним чином:

a>b якщо a.p1>b.p1 або a.p1==a.p2 && a.p1>b.p2

та впорядкувати масив;

г) ввести відношення часткового порядку,аналогічне п.в), для n елементів, вважаючи, що відповідні поля записані у вигляді масиву

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

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

TAnyClass.

4.Протестувати приклад з класом a_iterator.

5.Перевантажити операції new-delete для класу Complex так, щоб вони обробляли вимоги у виділенні пам’яті довільного розміру.

6.С++ не перевіряє автоматично під час виконання ситуацію виходу індекса за границю масива. Розробити тип даних масив, який виконує перевірку границь.

7.Написати тип string , що має ряд специфічних властивостей (наприклад, лічильник звернень, перегружені оператори +,-, приведення типу тощо).

8.Реалізувати загальний клас - ітератор списку, який має операції повернення слідуючого елемента, попереднього елемента та голови списку.

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

10.Розробити двозв’язний список, реалізація якого використовує лише один зв’язаний вказівник. Включити в розробку ітератор, який

295

може проходити список в будь-якому напрямі.

11. Розробити тип “розумного” вказівника, який гарантує вказування лише на вільну пам’ять.

8.13 ШАБЛОНИ С++.

Под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в.

8.13.1 ФУНКЦІОНАЛЬНІ ШАБЛОНИ

8.13.1.1 Визначення та використання шаблонів функцiй.

Шаблон функцiї - це узагальнене визначення, з якого компiлятор може

автоматично генерувати код (створити представника) функцiї. Синтаксис шаблона функцiї має наступний вигляд:

template <список_аргументiв_шаблона> тип_результату iм'я_функцiї (параметри ...)

{// Тiло функцiї

}

За ключовим словом template слiдує один чи декiлька аргументiв, якi помiщенi в дужки < > (це вимога синтаксису!) i вiдокремленi один вiд одного комами. Кожний аргумент складається з ключового слова class та iдентифiкатора, який визначає тип. Потiм записується визначення функцiї.

Приклади:

template <class T> void func( T t )

{

// тiло функцiї

}

template <class T>

void func_1( T t1, T t2 )

{

296

// тiло функцiї

}

template <class T1, class T2> void func_2( T1 t1, T2 t2 )

{

// тiло функцiї

}

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

Приклад:

#if !defined(__SORTTMPL_H) #define __SORTTMPL_H

template <class T>

void sort( T array[ ], size_t size )

{for( int i=0; i<size-1; i++ ) for( int j=size-1; i<j; j-- ) if ( array[j-1] < array[j] ) {T tmp =array[j]; array[j]=array[j-1]; array[j-1] =tmp;}

}

#endif // __SORTTMPL_H

Можна створити прототип шаблона функцiї у виглядi його попереднього оголошення:

template <class T> void sort(T array[ ],size_t size );

Пiсля визначення шаблону функцiї можна зразу її викликати: компiлятор автоматично створить представик тiла функцiї для викликiв, де конкретизуються параметри:

#include <iostream.h> int main (void )

{int myarray[ ] = { 30, 27, 45, 10, 23, 7, 4, 89 }; sort( myarray, sizeof( myarray )/sizeof( myarray[0] )

);

for (int i=0;i<sizeof(myarray)/sizeof(myarray[0]);i++)

cout << "myarray[" << i <<"]="<< myarray[i]<< endl; return 0;}

Шаблон sort можна використовувати i для класiв користувача, якщо клас містить перевантажену операцiю '<' , яку використовують в шаблонi для порiвняння елементiв.

Приклад:

297

class Prise {long gryv; int cop; public:

Prise( long _g, int _c ) {gryv =_g;

cop =_c;}

int operator < (const Prise& ) ;

friend ostream& operator << ( ostream &, Prise & ); };

int Prise::operator < ( const Prise& _amt ) { return ( gryv < _amt.gryv ) ||

( gryv == _amt.gryv &&

cop <

_amt.cop );}

ostream& operator << (

ostream&

os, Prise& _amt )

{ os << "UGR " << _amt.gryv << '.' << _amt.cop; return os;}

int main( void )

{

Prise dollar[ ] = { Prise( 5, 10 ), Prise( 5, 99 ), Prise( 4, 95 ), Prise( 6, 01 )};

sort( dollar, sizeof( dollar )/sizeof(dollar[0])); cout << "Курс долара по роках:\n";

for(int i=0;i<sizeof(dollar)/sizeof(dollar[0]);i++) cout<<"200"<< i << " = " << dollar[i]<< endl; return 0;}

Шаблон функцiї є визначенням, з якого компiлятор може створювати

функцiї. Функцiї, створенi по шаблону, називають iнодi функцiями шаблону або шаблонними функцiями.

8.13.1.2 Перевантаження шаблонiв функцiї.

Аналогічно, як i звичайнi функцiї, функцiональнi шаблони можуть бути перевантаженi.

Приклад:

template <class T>

T getMax( T t1, T t2 )

{return t1 > t2 ? t1 : t2;} template <class T>

T getMax(T t[ ], size_t size )

298

{T max = t[0];

for( int i=0; i<size; i++ ) if ( t[i] > max )

max = t[i]; return max;} int main( void )

{int i1 = 3; int i2 = 5;

int iarray[ ] = { 3, 9, 5, 8 };

cout << "max int = " << getMax( i1, i2 ) <<

end1;

cout << "max int = " << getMax(iarray, sizeof(iarray)/sizeof(iarray[0])) << endl;

return 0;

}

8.13.1.3 Cпецiалiзованi функцiї шаблона.

Спецiалiзована функцiя шаблона - це звичайна функцiя, iм'я якої спiвпадає з iменем функцiї в шаблонi, але яка визначається для параметрiв специфiчних типiв. Можна визначати спецiалiзованi функцiї шаблона для випадкiв, коли узагальнений шаблон не придатний для деякого типу даних. Наприклад, функцiя шаблона getMax не може використовуватися для рядкiв (char*, char[]), оскiльки код, який генерується компiлятором, просто буде порiвнювати їх положення в пам'ятi.

Приклад:

template <class T>

T getMax( T t1, T t2 )

{ return t1 > t2 ? t1 : t2;} char* getMax( char* s1, char* s2 )

{return strcmp( s1, s2 ) > 0 ? s1 : s2;} int main( void ){

int i1 = 3; int i2 = 5;

cout << "max int = "<<getMax(i1,i2)<<end1; char *s1 ="Golden Eagle";

char *s2 = "Peligrime Falcon";

cout << "max str=" <<getMax(s1,s2) << end1; return 0;}

Коли компiлятор зустрiчає виклик функції, то виконуються наступні дії:

-знаходить функцiю (не шаблонну), параметри якої вiдповiдають вказаним у виклику.

299

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

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

8.13.2 ШАБЛОНИ КЛАСІВ.

8.13.2.1 Визначення шаблонів класу

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

template <список_аргументiв_шаблона> class iм'я_ класу

{

// поля даних та функції члени

};

За ключовим словом template слiдує один чи декiлька аргументiв, помiщених в дужки < > i вiдокремлених одне вiд одного комами. Кожний аргумент є або iм'ям типу, за яким слідує iдентифiкатор, або ключовим словом class, за яким слідує iдентифiкатор типу.

Параметри шаблону, що складаються з ключового слова class та iдентифiкатора називають параметрами типу. Вони iнформують компiлятор про те, що шаблон пропонує тип в якостi аргумента. Параметри шаблона, що складаються з iменi типу та iдентифiкатора називаються нетиповими. Вони iнформують компiлятор про те, що в якостi аргумента виступає константа.

Приклад:

const size_t defStackSize = 512; template <class T>

class Stack

{public:

Stack( size_t size = defStackSize )

{numitems = 0;

items = new T[size];} ~Stack()

{ delete [ ] items;} void push(T t);

T pop();

300

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