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

osn_progr_final

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

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

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

ім’я_класу.ім’я_функції(список фактичних параметрів).

Таке повідомлення викликає якусь дію, реакцію об’єкта. Можемо вважати, що звертання до звичайного поля даних класу також є елементарним повідомленням (повідомленням без параметрів): ім’я_класу.ім’я_поля.

При такому підході ООП - програма являє собою послiдовнiсть виразiв, у яких об’єктам передаються деякі повідомлення.

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

ЗАВДАННЯ

1.Описати клас Сомplex , що містить поля даних - дійсну та уявну частину комплексного числа. Написати програму для ілюстрації доступу до полів даних з різними специфікаторами доступу.

2.Описати клас , полями якого є змінна типу FILE* та функціі запису та читання з файлу.

3.Описати клас Літак, який максимально підходить для моделювання реального об’єкта (відкриті поля даних - положення, тип, розміри, колір тощо; закриті - паливо, боєприпаси, справність систем. Написати методи руху літака , його зображення в графічному режимі.

4.Описати (на основі класу Timer) клас для моделювання годинника, який містить методи малювання годинника та показує дату та час при передачі йому деякого повідомлення :

а) за допомогою натискання деякої “гарячої” клавіші; б) за допомогою маніпулятора “миші”;

5.Описати клас Океан, який містить поле -двовимірний масив елементів, які визначають стан океану:

0- в даній частині немає нічого

1-N -в даній частині знаходиться рибка(чи тварина) відповідного типу N+1 -в даній частині знаходиться певний статичний об’єкт(перешкода)

6.Описати клас Клітина , який містить а) інформацію про стан 8 різних елементів клітини (ядро, ядерце,тощо) у ви-

гляді бітових полів деякої змінної (1-елемент здоровий, 0-пошкоджений або ві-

221

дсутній).

б) набір векторів, що задають просторовий напрям можливого поділу клітини(породження нової).

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

8.4 ДЕЯКІ ОСОБЛИВОСТI C++. 8.4.1 Коментарі.

Як відомо, коментарі в базовому С задаються за допомогою комбінації символів /* - */ наступним способом:

/* текст коментаря */.

В С++ для задання коментарів може використовуватись і подвійний слеш:

// текст коментаря

Подвійний слеш ставиться на початку кожного рядка, який буде коментарем. В кінці коментаря (закоментованого рядка) ставиться символ переведення рядка. Всередині тексту коментаря допускаються будь-які символи (включаючи //).

8.4.2 Прототипи функцій.

Прототип функцiї - це ім’я функції та її сигнатура(список типів параметрів):

returnType functionName (type1 par1, type2 par2,..., typeN parN);

Як відомо, в ANSI С функцію можна оголошувати без сигнату-

ри:

int f();

При цьому компiлятор не перевiряє вiдповiднiсть типiв параметрів, кiлькiсть параметрiв при виклику функції. Якщо при виклику функції параметри не відповідають типам (наприклад, розміщені не в тому порядку) чи вказана невірна їх кількість, то компілятор не помітить помилки. Вона може проявитись на етапі виконання програми. Тому в С++ рекомендується використовувати повні прототипи функцiй при оголошенні. Якщо, наприклад, в “старому стилі” функція f оголошується так:

void f();

то в С++:

void f(void);

При використанні старого стилю оголошення функцій С++ компілятор буде видавати відповідні попередження. Тому при компі-

222

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

8.4.3 Операція розширення області видимості.

Синтаксично позначається як подвійна двокрапка. В С++ використовується в двох основних випадках:

а) для доступу до змiнних, якi невидимi в данiй областi види-

мостi;

б) при визначеннi функцiй - членiв класу за межами формального опису класу.

Розглянемо ситуацію а) ( б) буде розглянуто пізніше) . Нехай маємо наступний фрагмент програми:

#include <stdio.h> float r=2.6;

int increment (int k) { int r=k+16;

printf("в функцiї increment r=%d\n",r); printf("значення ззовнi increment r=%0.2f\n",::r);

return r;

}

void main (void)

{

int r=increment(20);

printf("в функцiї main r=%d\n",r); printf("значення ззовнi main r =%0.2f\n",::r);

}

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

в функцiї increment r= 36

значення ззовнi

increment r=2.60

в функцiї

main r= 36

значення ззовнi main r =2.60

8.4.4 Оголошення в операторах.

В С++ знято обмеження на розміщення описувачів. Як відомо, в ANSI С описувач може міститись в тілі будь-якого складеного опера-

тора (причому синтаксично більшість компіляторів вимагають його ро-

зміщення на початку тіла складеного оператора). В С++ змінну можна описувати будь-де, навіть всередині оператора:

for (int i=1;i<5;i++);

223

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

8.4.5 ПЕРЕВАНТАЖЕННЯ ФУНКЦІЙ.

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

цій FuncName виду:

<type1> FuncName (<type11> x1, <type12> x2,..., <type1N1> xN1)

{ //тіло функції}

<type2> FuncName (<type21> x1,<type22> x2,..., <type2N2> xN2)

{ //тіло функції}

......................................................................

<typeM> FuncName (<typeM1> x1, <typeM2> x2,..., <typeMNM> XNM )

{ //тіло функції}

Тут typeIJ - певний тип даних, xIJідентифікатори змінних, N1 , N 2 ,

..., N M - кількості аргументів функцій. Приклад:

int Name (int first) {return first*first;} int Name (unsigned first) {return first*first;}

int Name (int first,char*second) {return first*strlen (second);} main()

{printf ("%d\n",Name (4)); printf ("%d\n";(unsigned)4); printf ("%d\n",Name (4,"abc");}

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

float Name (int first) {return first*first;}

то буде видане повідомлення про помилку на етапі компіляції.

8.4.6 Значення формальних параметрів по замовчуванню.

В мові С++ при визначенні функцій допускається конструкція виду:

<ReturnType> FunctionName (<type1> x1, <type2> x2,...,<typen> xn, <type n1> xn1=v1, <typen2> xn2=v2 ,..., <typenk> xnk=vk)

де ReturnType, typej - ідентифікатори типів даних, FunctionName- іден-

224

тифікатор імені функції, vj- константні вирази. Бачимо, що параметрам xn1, xn2,..., xnk присвоюються константні вирази v1,v2,...,vk безпо- середньо в сигнатурі функції при її визначенні. Ці параметри будемо називати параметрами по замовчуванню. Діє правило: якщо параметр є параметром по замовчуванню , то всi параметри , якi стоять справа вiд нього, теж повиннi бути параметрами по замовчуванню. Специфіка параметрів по замовчуванню полягає в тому, що всі вони чи частина з них може бути відсутньою в момент виклику функції. Тоді, якщо в момент виклику кількість фактичних параметрів не менша кількості звичайних параметрів функції і не більша кількості всіх можливих параметрів ( в противному випадку буде помилка), то після ініціалізації всіх можливих параметрів зліва направо, компілятор буде вважати, що всі інші параметри ініціалізовані значеннями, які присвоєні їм в сигнатурі .

Розглянемо приклад. Нехай визначені такі функції: void Name1 (float x,int y,char z='b') {printf ("x=%0.1f y=%d z=%c\n",x,y,z)}

void Name2 (float x,int y=16,char z='a') {printf ("x=%0.1f y=%d z=%c\n",x,y,z)}

Тоді допускається такий їх виклик:

Name1(1.0,5,’g’);

Name1(1.0,5);

Name2(2.0,25,'a');

Name2(2.0,25);

Name2(2.0);

8.4.7 Посилання та вказівники.

Операцiя адресацiї буде посиланням, якщо вона вжита в наступному контекcтi:

type &variablename=initexspression;

Тут variablename - це ідентифікатор змінної-посилання, що визначається, initexspression - це об’єкт, що має адресу в пам’яті(інколи використовується поняття L-вираз ). L-вираз (Lvalue-expression) - це вираз, що посилається на комірку пам’яті і тому має зміст в лівій частині бінарної операції присвоювання. Найпростіший приклад L- виразу є ідентифікатор вже визначеної змінної. Він посилається на комірку пам’яті, де зберігається значення цієї змінної. Посилання variablename визначає мiсце знаходження в пам'ятi iнiцiалiзуючого виразу.

Змiнна посилання повинна бути обов'язково проiнiцiалiзованою. Оголошення виду Type & varname; не допускаються. Розглянемо

225

наступний фрагмент програми: int y=16;

int & x=y;

printf ("x=%d\n",x); y=12;

printf ("x=%d\n",x);

Результат: x=16 y=12

Оголошення int &x=y фактично визначає 2 ідентифікатора, що пов’язуються з однією і тою ж коміркою пам’яті. При зміні значення одного ідентифікатора змінюється значення і другого. Змінна x обробляється компілятором як звичайна змінна типу int. При звертанні до такої змінної немає потреби в операції зняття посилання чи якихнебуть інших дій. Очевидно, що описаний вище фрагмент програми можна було б переписати з використанням вказівників:

int y=16; int *x=&y;

printf ("x=%d\n",*x); y=12;

printf ("x=%d\n",*x);

Бачимо, що посилання суттєво відрізняються від вказівників. Найчастiше посилання використовуються як формальнi парамет-

ри функцiй. Це робиться з двох основних причин:

а) для пiдтримки режиму зв'язування (аналог var в Pascal); б) щоб не допустити копiювання змiнних в стек.

Розглянемо приклад:

void increment (int& x)

{x+=1; }

void increment (int* x) { *x=*x+1; }

main( )

{

int v=3; increment (v); int anotherv=5;

increment (& anotherv); printf ("v=%d\n",v);

printf ("anotherv=%d\n",anotherv);}

Результати: v=4 anotherv=6

Конструкції int& x та int* x - це рiзнi сигнатури . Тому , з враху-

226

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

int *v1; *v1=6; increment (v1);

Використання посилань як формальних параметрiв функцiй має ряд особливостей. Розглянемо наступну програму:

void changev (int&p) {p+=20;

printf ("значення p в функцiї =%d\n",p);} main()

{int x=50; changev (x);

printf ("x=%d\n",x); int y=100;

changev (x+y); printf("x=%d y=%d\n",x,y); float r= -1.5;

changev (r);

printf ("r=%0.2f\n",r);}

Результати:

значення p в функцiї =70 x=70

значення p в функцiї =190 x=70 y=100

значення p в функцiї =19 r=-1.50

Результат першого виклику функції changev в функції main() пояснюється елементарно. При виклиці функції changev(x+y) створюється локальна копія формального параметра , з якою працює функція (параметри передаються по значенню). В третьому випадку компілятор не може зв’язати посилання на тип int з змінною типу float. Тоді створюється тимчасова змінна, яка і зв’язується з р:

float r=-1.5; int temp=r; changev (temp);

Iнiцiалiзувавши посилання , йому не можна присвоїти iншi значення:

int ivar=12;

227

int &iref=ivar;

/*Посилання на ivar*/

int anotherint;

 

iref=anotherint;

// не допускається

Посилання можуть повертатися функцiями: double & ref (int d);.

Функцiї-посилання використовуються для закриття внутрiшнього представлення структури даних.

Приклад:

#define FALSE 0 #define SIZE 100 #define BUFLEN 100 double &ref (int index); void showarray (void); double array [SIZE]; main ()

{int done=FALSE, index; char buffer [BUFLEN];

for (index=0;index< SIZE;index++) ref (index)=index;

while (!done) {showarray();

cout<<"\n введiть index вiд 0 до 9\n"; cin.getline (buffer,buflen) done=(strlen(buffer)==0);

if (!done){

index=ataf(buffer); // перетворення рядка в число cout<<"введiть значення типу float";

cin.getline (buffer,buflen);

/*введення значення в буфер*/ ref (index)=ataf (buffer);}}

return 0;}

double & ref (int index) {if (index<0) index=0; return array [index];} void showarray (void) {cont <<"\n array:\n"; for (int i=0;i< SIZE;i++)

cout <<"["<<i<<"]"<<array[i]<<'\n';}

В даній реалізації функція ref() повертає посилання на елемент масиву. Тоді конструкція ref(index) є посиланням на відповідний елемент масиву, що визначається змінною index. Тут ми “ховаємо” справжню внутрішню структура даних (в даному випадку масиву) і залишаємо відомою користувачу лише функцію, що з цими даними

228

працює.

8.4.8 Специфікатор INLINE

Використовується для того , щоб компiлятор помiщав код функцiї безпосередньо в місце її виклику. При цьому зростає швидкодія програми, проте і збільшується об’єктний код.

Приклад:

void notinline (void){ } inline void isinline (void){ } main()

{printf("starting notinline \n");

for (long i=1;i<5000000;i++) notinline (); printf ("starting isinline\n");

for (i=1;i<5000000;i++) isinline ();}

Зisinline функцiя виконується в кілька разiв швидше.

8.4.9Операції NEW та DELETE .

Операція new служить для виділення пам’яті. Синтаксично допускається 3 способи її використання :

1. new type_name

Приклад: float *r=new float;

При такому оголошенні r буде вказівником на значення типу float, причому вказувати він буде на початок вже виділеної області пам’яті розміром float, на відміну від оголошення float *r; , де пам’ять не виділяється.

2. new (type_name)

Приклад: float*r=new(float);

Цей випадок аналогічний попередньому.

3. new type_name [expression]

Приклад: float*r=new float [20];

Операція Delete служить для звільнення пам’яті в “кучі”. Відповідно до операції new, синтаксично допускаються такі способи її використання

1.delete var_name;

Приклад: float*r=new float [20]; delete r;

2.delete [expr] var_name

Приклад:

float*r=new float [20]; delete [20] r;

Відмітимо, що дія в першому та другому випадках анологічна. Звідси випливає, що другий спосіб використання не дасть можливості звіль-

229

нити не всю пам’ять, виділену за допомогою операції new (в ранніх версіях С++). Виділивши пам’ять , наприклад, так:

float*r=new float [20];

можемо звільнити її будь-яким з наступних способів: delete [200] r;delete [20] r;

delete [10] r; delete [2] r; delete [ ] r; delete r;

8.4.10 Вказівник на void.

Вказiвнику на тип void можна присвоїти значення вказiвника на будь-який базовий тип.

Приклад: void* void_ptr;

int* int_ptr=0x256; void_ptr=int_ptr; int_ptr=void_ptr; //невiрно

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

void swap (void* & item1,void* & item2) {void* temp=item1;

item1=item2;

item2=temp;}

main()

{int *i=new int, *j=new int; *i=5;

*j=20; swap (i,j);

print("*i=%d*j=%d\n",*i,*j); float *y=new float;*x=new float; *x=5.0;

*y=20.0; swap (x,y);

printf("*x=%f *j=%f\n",*i,*j);}

*Примітка. В деяких версіях компілятора С++ (наприклад, Borland 3.11) цей приклад не працює. Тоді необхідно явно вказувати перетворення типу до void*:

swap((void*) i, (void*) j );

8.4.11 Зв’язування із збереженням типів

В С++ всі імена функцій кодуються компілятором. Тому якщо програміст хоче використовувати С -функції, то вони повинні бути

230

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