36618

ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ

Конспект

Информатика, кибернетика и программирование

Об’єктноорієнтоване програмування це методологія програмування яка базується на поданні програми у вигляді сукупності об’єктів кожний із яких є реалізацією певного класу а класи утворюють ієрархію на принципах успадкування. кожний об’єкт є реалізацією певного класу; 3. Це обєкт класу який асоційований з системною консоллю і отже все те що буде передано в цей об'єкт за допомогою оператора буде виведено в консоль. Тема 15 Поняття класу.

Украинкский

2013-09-23

3.53 MB

18 чел.

Міністерство освіти і науки України

Запорізький електротехнічний коледж

Запорізького національного технічного університету

ЗАТВЕРДЖЕНО

Протокол засідання ПЦК “Розробка програмного забезпечення”

від ___________________ № _____

Голова ПЦК _________ Н.В. Бабенко

ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ 

Конспект лекцій для спеціальності

«Розробка програмного забезпечення»

Викладач      О.М.Ткачук

2010


Конспект лекцій з предмета «Об’єктно-орієнтоване програмування» для студентів денного відділення спеціальності «Розробка програмного забезпечення» розглянуто на засіданні методичної ради коледжу та рекомендовано для використання у навчальному процесі.

Секретар методичної ради   В.В Кузьменкова

Конспект лекцій з предмета «Об’єктно-орієнтоване програмування» для студентів денного відділення спеціальності «Розробка програмного забезпечення»  оформлено згідно з вимогами стандартів коледжу.

Фахівець зі стандартизації    В.О. Білий


Передмова

Конспект лекцій з предмета «Об’єктно-орієнтоване програмування» для студентів денного відділення спеціальності «Розробка програмного забезпечення». Вищеназваний предмет відноситься до предметів професійно-практичної підготовки навчального плану спеціальності, створеного на основі галузевого стандарту вищої освіти з підготовки молодших спеціалістів за спеціальністю 5.05010301 «Розробка програмного забезпечення», затвердженого  в 2009 р.

Для успішного оволодіння знанням з предмету «Об’єктно-орієнтоване програмування» необхідно мати знання з предметів «Основи програмування та алгоритмічної мови», тощо.

Знання, які одержать студенти після вивчення предмета, можуть застосовуватися при вивчення предмета «Проектування автоматизованих інформаційних систем», «Комп’ютерні мережі», а також в курсовому та дипломному проектуванні.

Компетенції, якими повинен оволодіти студент:

здатністю розробляти алгоритми та структури даних для програмних продуктів;

- вмінням проектувати компоненти архітектурного рішення, використовуючи відповідні програмні засоби;

- здатністю створювати людино-машинний інтерфейс, використовуючи засоби візуального програмування;

- володіти основами конструювання програмного забезпечення;

- володіти основами методів та технологій об'єктно-орієнтованого програмування;

- базовими уявленнями про сучасні стандарти  та процеси управління якістю програмного забезпечення  

- вмінням застосовувати та створювати компоненти багаторазового використання, використовуючи відповідні програмні засоби.


Зміст

[1]
Вступ

[1.1] 7.2 Цикл while и do while.

[1.2] 7.3 Використання вкладених циклів.

[1.2.1] 11.2.1 Передача параметру по значенню

[1.2.2] 11.2.2 Передача параметру по посиланню

[1.2.3] 11.2.3 Параметри за замовчуванням

[1.2.4] 11.2.4 Покажчик

[1.2.5] 11.3.1 Одномірний масив як параметр

[1.2.6] 11.3.2 Двовимірний масив як параметр

[1.2.7] 11.3.3 Рядок як параметр


Вступ

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

Типові представники мов ООП: Object Pascal, С++, C#, Java. Основним елементом конструкції в них є модуль, до складу якого входять логічно пов’язані класи та об’єкти.

Визначимо це так: якщо уявити процедури та функції у вигляді дієслів, а дані у вигляді іменників, то процедурні програми будуються із дієслів, а об’єктно-орієнтовані – з іменників. З цієї причини структура програм малої та середньої складності при ООП подається графом, а не деревом. Крім того, скорочена чи й відсутня область глобальних даних. Дані та дії організуються тепер у такий спосіб, що основою конструкції стають класи та об’єкти, а не алгоритми.

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

Методи структурного проектування мали на меті спростити процес розробки складних систем на основі алгоритмічного підходу.

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

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

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

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

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

У цьому визначенні можна вирізнити три частини:

1. ООП використовує як елементи конструкції об’єктів, а не алгоритми;

2. кожний об’єкт є реалізацією певного класу;

3. класи організовані ієрархічно.

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


Модуль 1 Бібліотека STL Visual C++

Тема 1 Структура програми та елементи мови С++

План

1.1 Розвиток мови С++

1.2 Структура програми на мові С++

1.3 Елементи програми на мові С++

1.1 Розвиток мови С++

C++ — універсальна мова програмування високого рівня з підтримкою декількох парадигм програмування: об'єктно-орієнтованої, узагальненої та процедурної. Розроблена у 1979 році на основі мови С та названа «С з класами». Перейменована у C++ у 1983 р.

У 1990-х роках С++ стала однією з найуживаніших мов програмування загального призначення.

При створенні С++ розробники прагнули зберегти сумісність з мовою С. Більшість програм на С справно працюватимуть із компілятором С++. С++ має синтаксис, заснований на синтаксисі мови С.

При створенні мови її автори притримувались певних принципів, які в сукупності зумовили особливості мови С++. Деякі найважливіші принципи:

  1.  Отримати універсальну мову зі статичними типами даних, високою ефективністю та сумісністю з мовою С.
  2.  Підтримувати декілька парадигм програмування, проте основну увагу приділити ООП
  3.  Надати програмісту свободу вибору, навіть якщо це може бути хибний вибір.
  4.  Максимально зберегти сумісність з мовою С, зробивши легким перехід від програмування на мові С.
  5.  Не використовувати особливості, що не є універсальними, наприклад, що залежать від певної апаратної платформи.
  6.  Зробити максимально просте середовище програмування.

1.2 Структура програми на мові С++

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

Оголошення певних елементів програми може бути виконано у тому ж програмному коді, що і їх подальше використання, а може – у окремому файлі. Такі файли називають «файлами заголовка» (header files), вони мають розширення .h та до основного коду програми їх приєднують за допомогою директиви препроцесора #include.

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

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

Якщо ж для прикладу узяти просту програму, код якої міститься в одному файлі і яка для своєї роботи використовує стандартні функції та класи мови С++, то її структуру можна уявити у наступній послідовності:

  1.  оголошення (підключення) стандартних функцій та класів за допомогою #include;
  2.  оголошення глобальних змінних, визначення прототипів власних функцій, класів та структур;
  3.  Реалізація головної функції програми;
  4.  Реалізація власних функцій та методів класів.

1.3 Елементи програми на мові С++

Перед тим, як описати основні елементи мови С++, розглянемо приклад простої програми:

/*Програма, яка виводить до консолі

надпись Hello, World!!!*/

#include <iostream>

using namespace std;

int main()

{  printf(“Hello, World!!!”); //виведення рядка до консолі

 return 0;}

Головна функція програми - main() – функція, що завжди присутня у програмі на мові С++. Під час роботи програми виконуються тільки ті дії, що були описані у функції main(). Головна функція може бути лише одна на всю програму.

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

Коментар – текст, що не сприймається компілятором та необхідний для того, щоб робити примітки різного призначення. Коментарі бувають багаторядкові – це увесь текст від комбінації символів /* до комбінації символів */ та однорядкові – увесь текст у рядку після комбінації //.

Оператори – символи або слова, що вказують на виконання певної дії. Можуть викликатися з параметрами або без них. Наприклад, у даній програмі оператор return з параметром 0 вказує на те, що необхідно закінчити виконання функції main() з кодом 0.

Виклик функцій, що використовуються для виконання певних дій. Для виклику функції необхідно вказати її ім’я та у дужках вказати необхідні параметри, якщо вони є. Наприклад, у даній програмі присутній виклик функції printf() з параметром “Hello, World!!!”. При виконанні цього коду програма виведе до консолі рядок “Hello, World!!!”.


Тема 2 Ідентифікатори. Типи даних мови С++

План

2.1 Поняття ідентифікатора, змінної

2.2 Типи даних мови С++

2.1 Поняття ідентифікатора, змінної

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

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

При оголошенні ідентифікаторів слід пам'ятати про те, що мова С++ (як і багато інших мов високого рівня) розрізняє рядкові і заголовні букви, тому для нього, наприклад, Abc, ABC, abc - три абсолютно різних ідентифікатора.

Ряд слів у мові C++ має особливе значення і не може використовуватися в якості ідентифікаторів. Такі зарезервовані слова називаються ключовими. Більшість ключових слів є операторами.

Змінна - це символічне позначення будь-якої змінною величини в програмі.

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

Перш ніж використовувати змінну, її обов'язково необхідно оголосити. Оголошення змінної включає в себе необов'язкове вказівку модифікаторів типу, обов'язкова вказівка типу даних і обов'язкова вказівка ідентифікатора (імені) змінної. Наприклад:unsigned int x;

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

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

2.2 Типи даних мови С++

Мова С++ - це строго типізований мову. Будь-яка величина, використовувана в програмі, належить до якого-небудь типу. При будь-якому використанні змінних у програмі перевіряється, чи можна застосувати вираз або операція до типу змінної. Досить часто зміст виразу залежить від типу беруть участь у ньому змінних.

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

Всі елементарні типи даних мови С++ можна поділити на три категорії: цілочисельні, плаваючі, нетипізовані.

Цілочисельні типи даних призначені для зберігання в пам'яті цілих десяткових чисел. Значення змінних цих типів може бути інтерпретоване як ціле число або символ. В описі ідентифікаторів для цілочисельних типів даних можуть використовуватися такі модифікатори як signed і unsigned, які відповідно означають, що число зберігається із знаком або без знаку. По-замовчуванню більшість компіляторів представляють цілочисельні типи даних як знакові (табл. 2.1).

Таблиця 2.1 – Цілочисельні типи даних

Опис типу

Ключове слово

Розмір в пам'яті, байт

Діапазон збережених значень

Символьний

char (signed char)

1

-27 .. 27-1

unsigned char

1

0 .. 28-1

Коротке ціле

short (signed short)

2

-215 .. 215-1

unsigned short

2

0 .. 216-1

Ціле

int (signed int)

2-4

unsigned int

2-4

Довге ціле

long (signed long)

4

-231 .. 231-1

unsigned long

4

0 .. 232-1

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

Таблиця 2.2 – Цілочисельні  типи даних

Опис типу

Ключове слово

Розмір в пам’яті, байт

Діапазон збережених значень

Низької точності

float

4

3.4E-38 .. 3.4E+38

Середньої точності

double

8

1.7E-308 .. 1.7E+308

Високої точності

long double

10

3.4E-4932 .. 3.4E+4932

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

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

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


Тема 3 Функції та потоки введення/виведення

План

3.1 Бібліотека iostream

3.2 Функція виведення printf()

3.3 Виведення до консолі за допомогою потоку

3.1 Бібліотека iostream

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

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

Для використання функцій і потоків введення / виведення необхідно за допомогою директиви препроцесора # include підключити файл iostream. Після необхідно обов'язково зареєструвати стандартні класи бібліотеки за допомогою оператора using namespace з параметром std.

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

#include <iostream>

using namespace std;

int main()

{

printf(“This text is printed with PRINTF”);

printf(“\n”);

cout << “This text is printed with COUT”;

cout << endl;

return 0;

}

Функція printf () служить для форматованого виведення в консоль значень змінних різних типів. Функція може мати від одного до безлічі параметрів, першим з яких обов'язково є рядок формату. Інші параметри функції – це змінні, значення яких і будуть виводитися відповідно до правил, зазначених в рядку формату.

printf("рядок формату",змінна1,змінна2, …, зміннаN, …);

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

% [прапор] [ширина] [.точність] [розмір] специфікатор_типу

З усіх перелічених символів обов'язковим є тільки специфікатор – позначає тип змінної (табл. 3.1). Змінна виводиться в вигляді відповідного типу. Можливі наступні специфікатори_типу:

Таблиця 3.1 – Специфікатори типу

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

Значення

Приклад

c

символ

a

d чи i

ціле десяткове число

39265

e чи E

десяткове число в експоненційному вигляді

3.9265e+2

f

десяткове число з плаваючою крапкою

392.65

o

число в восьмирічній системі числення

610

s

Рядок символів

sample

u

Без знакове десяткове число

7235

x чи X

число в шістнадцятковій системі числення

7fa

p чи  n

покажчик

B800:0000

%

символ%

Приклад 1

int main(){

int a = 78;

printf("%c \n",a);//1

printf("%d \n",a);//2

printf("%o \n",a);//3

printf("%x \n",a);//4

printf("%X \n",a);//5

printf("%p \n",a);//6

 

double b = 0.3451242343754734;

printf("%f \n",b);//7

printf("%e \n",b);//8

printf("%E \n",b);//9

printf("%s \n", "String");//10

return 0;

}

Результат:

N   //1

78   //2

116   //3

4e   //4

4E   //5

0000004E  //6

0.345124  //7

3.451242e-001 //8

3.451242E-001 //9

String   //10

Значення прапора може бути наступним (табл. 3.2):

Таблиця 3.2 – Значення поля «прапор»

Знак

Назва знаку

Значення

При відсутності цього знаку

Примітка

-

дефіс

вирівнюється значення по лівому краю в межах ширини поля

по правому

+

плюс

завжди вказувати знак (плюс чи мінус) для вивединого десяткового числового значення

Тільки для від’ємних чисел

 

пробіл

Розміщувати перед результатом пробіл, якщо перший символ значення не знак

Вивід може починатися з цифри.

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

#

октоторп

«альтернативна форма» виводу значення

див. нижче

0

нуль

доповнювати поле до ширини, вказаної в полі ширина правила, символом 0

дополнять пробелами

Використовується для типів d, i, o, u, x, X, a, A, e, E, f, F, g, G. Для типів d, i, o, u, x, X, якщо точність вказана, цей прапор ігнорується. Для остальных типов поведінка не визначена.

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

Точність вказується починаючи з точки і означає кількість виведених десяткових знаків.

Таблиця 3.3 – Значення поля «розмір»

Розмір

%d, %i, %o, %u, %x, %X

%n

відсутній

int або unsigned int

покажчик на int

l

long int або unsigned long int

покажчик на long int

hh

Аргумент має тип int або unsigned int, але примусово приводиться до типу signed char або unsigned char, відповідно

покажчик на signed char

h

Аргумент має тип int або unsigned int, але примусово приводиться до типу short int або unsigned short int, відповідно

указатель на short int

Поле розмір (табл. 3.3) дозволяє вказати розмір даних, переданих функції. Необхідність у цьому полі пояснюється особливостями передачі довільної кількості параметрів у функцію в мові С++: функція не може «самостійно» визначити тип і розмір переданих даних, так що інформація про тип параметрів і точному їх розмірі повинна передаватися явно:

  1.  аргументи типу float приводяться до типу double;
  2.  аргументи типів unsigned char, unsigned short, signed char і short приводяться до одного з наступних типів:
    •  int, якщо цей тип здатний представити всі значення вихідного типу;
    •  unsigned у противному разі;
  3.  аргументи типу bool приводяться до типу int.

Таким чином, функція printf не може отримувати аргументів типів float, bool або цілочисельних менших за int чи unsigned int.

Приклад 2:

int main(){

char ch = 'X';

printf("%5c \n",ch); //1

printf("%-5c \n",ch);//2

 

short a = 115;

printf("%i \n", a);//3

printf("%+i \n", a);//4

printf("% i \n",a);//5

printf("%#x \n",a);//6

 

long b = 543627;

printf("%8i \n", b);//7

printf("%08i \n", b);//8

double c = 0.1 / 0.3;

printf("%.2f \n", c);//9

printf("%7.3f \n", c);//10

printf("%-4c%-5i%-7i%5.1f \n",ch,a,b,c);//11

return 0;

}

Результат:

   X    //1

X     //2

115     //3

+115     //4

115     //5

0x73     //6

 543627    //7

00543627    //8

0.33     //9

 0.333    //10

X   115  543627   0.3 //11

Крім правил виводу в рядку формату можна задавати також т.зв. керуючі послідовності символів, які не виводяться в консоль але змінюють формат виведення значення (табл. 3.4). Керуюча послідовність складається зі знаку \ (зворотна похила риска) і одного або декількох символів, кожен з яких має своє певне призначення.

Таблиця 3.4 – Керуючі послідовності символів

Послідовність

призначення

\n

перехід на новий рядок

\t

Табуляція горизонтальна

\v

Табуляція вертикальна

\r

повернення на початок рядка

\b

стирання попереднього символу

\a

звуковий сигнал

\\

зворотна похила риска

\'

одинарна лапка

\”

подвійна лапка

\ooo

ASCII символ, заданий восьмирічним кодом

\xhh

ASCII символ, заданий шістнадцятковим кодом

Приклад 3

int main(){

printf("abc\ndef\n"); //1

printf("a\tbc\tde\n"); //2

printf("abc\rde\n"); //3

printf("abc\bdef\n"); //4

printf("\\\'\"\n"); //5

printf("\144 \x64\n"); //6

return 0;

}

Результат:

Abc     //1

Def     //1

a       bc      de  //2

dec     //4

abdef    //4

\'"     //5

d d     //6

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

3.3 Виведення до консолі за допомогою потоку

Для виведення будь-якої інформації за допомогою потоку необхідно використовувати наступну послідовність операторів:

cout <<змінна1 <<змінна2 <<... <<зміннаN;

Строго кажучи, cout не є оператором. Це об'єкт класу, який асоційований з системною консоллю і, отже, все те, що буде передано в цей об'єкт за допомогою оператора <<, буде виведено в консоль.

Дані, які виводяться в потік також можна форматувати. По-перше, це можна робити, використовуючи керуючі послідовності, по-друге - застосовуючи спеціальні керуючі слова - маніпулятори потоку(наприклад: endl – перехід на новий рядок, застосовується в вигляді: cout << endl;). Маніпулятори передаються в об'єкт cout перед значенням змінної і безпосередньо в консоль не виводяться, але змінюють формат виведення значення.

Для введення даних можна використовувати об’єкт cin з оператором >>.


Тема 4 Арифметичні операції

План

4.1 Арифметичні дії над числами

4.2 Неявне перетворення типів

  1.  Арифметичні дії над числами

Арифметичні дії над числами називають операціями, самі числа – операндами, символи дій – операторами.

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

- - віднімання, тип результату не залежить від типу змінних;

+ - додавання, тип результату не залежить від типу змінних;

* - множення, тип результату не залежить від типу змінних;

/ - ділення, тип результату буде приведений до типу першого операнда;

% - залишок від ділення, може використовуватися лише зі змінними цілого типу.

4.2 Неявне перетворення типів

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

#include <iostream>

using namespace std;

int main()

{

 int x,y;

 x = 10;

 y = 3;

 cout << x/y << endl;

 cout << (float)x/y << endl;

}

Програма виведе до консолі наступну інформацію:

3

3.33333

У першому випадку ми виводимо до консолі результат ділення цілого числа на ціле, тобто за правилами мови С++ результат теж буде цілим числом. У другому випадку ми перед ділення перетворюємо тип першого операнда до плаваючого, тому результат теж буде плаваючим числом.


Тема 5 Математичні функції стандартної бібліотеки

План

5.1 Тригонометричні функції

5.2 Зворотні тригонометричні функції

5.3 Функції піднесення до ступеня

5.1 Тригонометричні функції

Тригонометричні функції сприймають у якості аргументу значення кута у радіанах. Тому для зручності користувача необхідно перетворювати градуси у радіани. Для цього введені до програми значення у градусах необхідно помножити на число Пі та поділити на 180.

Розглянемо приклади наступних тригонометричних функцій: sin(x), cos(x), tan(x), які повертають нам відповідно синус, косинус та тангенс числа х.

#include <iostream>

#include <math.h>

using namespace std;

#define PI 3.14159265

int main ()

{

 double angle, s, c, t;

cout << “Vvedite ugol\n”;

cin >> angle;

s = sin(angle*PI/180);

c = cos(angle*PI/180);

t = tan(angle*PI/180);

printf ("Sinus %f gradusov = %f\n”, angle, s);

printf ("Cosinus %f gradusov = %f\n”, angle, c);

printf ("Tangens %f gradusov = %f\n”, angle, t);

 return 0;

}

5.2 Зворотні тригонометричні функції

Зворотні тригонометричні функції asin(x), acos(x), atan(x) повертають значення кута у радіанах для відповідного значення синуса, косинуса та тангенса.

Приклад:

#include <iostream>

#include <math.h>

using namespace std;

#define PI 3.14159265

int main ()

{

 double x, as, ac, at;

 cout << “Vvedite znachenie ot 0 do 1\n”;

 cin >> x;

 as = asin(x);

 as = as*180.0/PI;

 ac = acos(x);

 ac = ac*180.0/PI;

 at = atan(x);

 at = at*180.0/PI;

 printf ("ArcSinus ot %f = %f gradusov\n”, x, as);

 printf ("ArcCosinus ot %f = %f gradusov\n”, x, ac);

 printf ("ArcTangens ot %f = %f gradusov\n”, x, at);

 return 0;

}

У даній програмі було використано зворотне перетворення з радіан до градусів.

5.3 Функції піднесення до ступеня

Функції піднесення до ступеня pow(x, a) та sqrt(x) дозволяють отримати відповідно XA та кадратний корень від Х.

#include <iostream>

#include <math.h>

using namespace std;

int main ()

{

 double x, step;

 cout << “Vvedite chislo i steepen dlya vozvedeniya\n”;

 cin >> x >> step;

 printf ("%f ^ %f = %f\n”, x, step, pow(x,step));

 printf ("Sqrt ot %f = %f\n”, pow(x,step), sqrt(pow(x,step));

 return 0;

}

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

Якщо ми введемо до цієї програми числа 4 та 6 то отримаємо у консолі наступну інформацію:

Vvedite chislo i steepen dlya vozvedeniya

4

6

4.000000 ^ 6.000000 = 4096.000000

Sqrt ot 4096.000000 = 64.000000

Press any key to continue

Функції округлення ceil(x) та floor(x) дозволяють зробити округлення відповідно до найбільшого та найменшого цілого. Функції округлення за правилами арифметики у стандартній бібліотеці С++ немає, проте вона реалізована у деяких сучасних компіляторах мови.

Логарифмічні функції log(x) та log10(x) дозволяють повернути відповідно натуральний та десятковий логарифми від числа.

Функції абсолютування abs(x) та fabs(x) дозволяють повернути абсолютне значення (значення по модулю) відповідно цілої та дійсної змінної.

Функція обчислення залишку від ділення fmod(x, y) дозволяє повернути залишок від ділення x на y, які можуть бути дійсними числами.


Тема 6 Логічні оператори. Розгалуження. Оператори вибору

План

6.1 Оператор if

6.2 Логічні операції

6.3 Операції відношення

6.4 Оператор switch

  1.  Оператор if

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

Алгоритм називається галуженням, якщо він складається з декількох блоків, гілок або частин, кожна з яких виконується в залежності від виконання деяких умов.

Найпростіша форма оператора if має вигляд:

if (умова) оператор;

Умова - це логічний вираз. Воно приймає значення або "істинне", або "хибне". У мові С + + прийнято, що значення "істинне" - це ненульове значення величини, яка записана і перевіряється в умові. Значення "хибне" - це нуль.

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

if (умова)

оператор1;

else

оператор2;

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

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

6.2 Логічні операції:

& & - І;

| | - АБО;

! - НЕ.

6.3 Операції відносини:

== - тотожно дорівнює;

! = - Не дорівнює;

<- більше;

<= - більше або дорівнює,

> - менше;

> = - менше або дорівнює.

6.4 Оператор switch

Оператор switch призначений для організації вибору з безлічі різних варіантів. Формат оператора наступний:

switch (вираз)

{case константа1:

список-операторів1 //не обов’язкові

case константа2:

список-операторів2//не обов’язкові

default:     //не обов’язковий

список операторів за замовчуванням

}

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

Значення цього виразу є ключовим для вибору з декількох варіантів. Тіло оператора switch складається з декількох операторів, помічених ключовим словом case з наступною константою.

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

В операторі switch може бути, але обов'язково один, фрагмент позначений ключовим словом default. Оператори, які записані після default виконуються в випадку, коли немає спів падань з case константами.

Список операторів може бути порожнім, або містити один або більше операторів. Причому в операторі switch не потрібно укладати послідовність операторів у фігурні дужки.

Схема виконання оператора switch наступна:

- обчислюється вираз в круглих дужках;

- Обчислені значення послідовно порівнюються з константами, наступними за ключовими словами case;

- якщо одна з констант збігається зі значенням виразу, то управління передається на оператор, позначений відповідним ключовим словом case;

- Якщо жодна з констант не співпадає з значенням виразу, то управління передається на оператор, позначений ключовим словом default, а в разі його відсутності управління передається на наступний після switch оператор.

Відзначимо цікаву особливість використання оператора switch: конструкція зі словом default може бути не останньою у тілі оператора switch. Ключові слова case і default в тілі оператора switch істотні тільки при початковій перевірці, коли визначається початкова точка виконання тіла оператора switch. Всі оператори, між початковим оператором і кінцем тіла, виконуються незалежно від ключових слів, якщо тільки якийсь з операторів не передасть управління з тіла оператора switch. Таким чином, програміст повинен сам подбає про вихід з case, якщо це необхідно. Найчастіше для цього використовується оператор break.


Тема 7 Цикли

План

7.1 Цикл for

7.2 Цикл while и do while

7.3 Використання вкладених циклів

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

1. підготовка циклу: завдання початкових значень змінним циклу перед його виконанням;

2. тіло циклу - дії, повторювані в циклі;

3. модіфікацію (зміна) значень змінних циклу перед кожним його повторенням;

4. управління циклом: перевірку умови закінчення циклу.

7.1 Цикл for.

За допомогою ключового слова for у мові C + + можна оголосити конструкцію, звану циклом з лічильником. У круглих дужках після слова for задаються три секції, розділені оператором «;». Кожна з цих секцій має своє чітко регламентоване призначення. Дії, зазначені в першій секції, відповідають за ініціалізацію початкових значень змінних циклу (лічильників). У другій секції записуються умови закінчення циклу. У третій секції записуються оператори зміни значень лічильників.

Після визначення циклу в операторних дужках вказуються оператори, складові безпосередньо тіло циклу.

Приклад:

int i;

for (i=0; i<10; i++)

cout << i << endl;

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

int i=0;

for (; i<10;)

{ cout << i << endl;

i++; }

Одноразове виконання всіх операторів тіла циклу називають ітерацією.

/ / Програма розраховує суму і

/ / середнє значення ряду цілих чисел, використовуючи

// цикл for.

# include <iostream>

int main ()

{

int count=0;

double sum=0;

int first, last, temp;

cout << "Введите первое целое:";

cin >> first;

cout << "Введите последнее целое:";

cin >> last;

if (first > last)

 { temp = first;

    first = last;

    last = temp;

  }

int i;

for (i=first; i<=last; i++)

{

count ++;

sum + = i;

}

cout << "сумма целых чисел"

<< first << "go" << last << "="

<< sum << endl;

cout << "среднее значение" << sum/count

return 0;

}

При використанні циклів для зміни значень лічильників зручно використовувати унарні оператори інкремент «+ +» і декремент «--», а також скорочену форму запису бінарних арифметичних операторів, наприклад i = i + 2 еквівалентно i + = 2.

7.2 Цикл while и do while.

Цикл while умовний цикл, в якому операції виконуються до тих пір, поки умова має значення true. Т.ч. цикл while може не виконати жодної операції, якщо перевіряється умова спочатку має значення false. На відміну від нього цикл do while завжди хоча б один раз обов'язково виконає оператори, що належать до тіла циклу. Загальний вигляд циклів:

while (условие)

{

…;

}

do

{

…;

}

while(условие);

7.3 Використання вкладених циклів.

Якщо в тілі одного циклу оголосити оператор іншого, то такі цикли називаються вкладеними. Цикл, оголошений в іншому циклі називається внутрішнім (вкладеним) в зовнішній цикл. Кожній ітерації зовнішнього циклу відповідає виконання усіх ітерацій вкладеного. Наприклад, розглянемо програму підрахунку кількості «щасливих квитків» з використанням шести циклів, вкладених один в одного.

# include <iostream>

int main ()

{

 int x1, x2, x3, x4, x5, x6, c=0;

 for (x1=0; x1<10; x1++)

   for (x2=0; x2<10; x2++)

     for (x3=0; x3<10; x3++)

       for (x4=0; x4<10; x4++)

         for (x5=0; x5<10; x5++)

           for (x6=0; x6<10; x6++)

             if ((x1+x2+x3)==(x4+x5+x6))

               c++;

 cout<<”c = ”<<c<<endl;

 return 0;

}


Тема 8 Масиви даних. Статичний та динамічний розподіл пам’яті

План

8.1 Поняття масиву

8.2 Статичні масиви

8.3 Динамічний розподіл пам'яті

8.1 Поняття масиву

Масив — сукупність величин одного типа (елементів), об'єднана загальним ім'ям. Елементи масиву розташовуються в елементах пам'яті послідовно. Доступ до кожного конкретного елементу здійснюється по його індексу – порядковому номеру. У мові С++ нумерація елементів масивів починається з 0. Масиви володіють наступними властивостями:

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

8.2 Статичні масиви

Найбільш простими для використання в програмах є статичні масиви, при оголошенні яких необхідно обов'язково вказати кількість елементів (розмір масиву). Кількість елементів статичного масиву повинна обов'язково задаватися константним числом і не може бути змінено в процесі компіляції або виконання програми. Для оголошення статичного масиву на мові С++ необхідно вказати типа даних, ім'я масиву і в квадратних дужках задати константне ціле число – кількість елементів масиву. Наприклад – масив цілих чисел на 15 елементів:

int mas[15];

При цьому слід пам'ятати, що індекси елементів масиву будуть від 0 до 14. Створюючи масив, також можна відразу ініціалізувати значення його елементів, при цьому явно вказувати розмір масиву необов'язково – С++ визначить його автоматично:

double x[] = {6.4, 5.657, 4.32, 5.62};

Для доступу до елементу масиву необхідно вказати його ім'я і в квадратних дужках індекс елементу. Наприклад, додавши до попереднього прикладу рядок

cout << x[2];

отримаємо вивід в консоль числа 4.32.

Для обробки елементів масиву зручно використовувати цикли. При ініціалізації циклу лічильник має бути заданий таким чином, щоб перебрати значення від 0 до останнього елементу масиву. У тілі циклу як індекс елементу масиву підставити змінну лічильника. Таким чином, кожну ітерацію циклу буде виконуватися вибірка наступного елементу масиву. Наприклад, програма, яка знаходить мінімальне число з 5 введених:

const int N = 5;//кількість елементів масиву

 int x[N];//масив на 5 елементів

cout << "Vvedit elementy masyvu\n";

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

 cin >> x[i];//введення елементів масиву

 int min = x[0];//ініціалізуємо змінну

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

 for(i = 1; i<N; i++)//перебираємо всі елементи масиву, окрім першого

{

 if(x[i] < min)//якщо значення поточного елементу менше за значення мінімума

  min = x[i];//привласнюємо змінній новий мінімум

 

}

cout << "Minimum = " << min << endl;

 return 0;

8.3 Динамічний розподіл пам'яті 

Використання статичних масивів накладає на розробника деякі досить жорсткі обмеження:

1.Розмір масиву має бути однозначно визначений до компіляції  програми.

2.Розмір масиву не може бути змінений в процесі виконання програми.

3.Масив не може бути примусово видалений з пам'яті програми. Набагато зручнішим, хоча і трохи більш складним є використання динамічних масивів. Для роботи з ними в мові С++ використовується надзвичайно потужний механізм покажчиків.

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

Оголошення покажчика нічим не відрізняється від оголошення звичайній змінній, але перед ідентифікатором ставиться знак *, наприклад:

int *mas;

оголосить покажчик на блок пам'яті, розрахований на зберігання цілочисельних змінних – динамічний масив цілих чисел.

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

mas = new int[10];

виділить блок пам'яті для зберігання 10 цілих чисел.

Робота з елементами динамічних масивів нічим не відрізняється від обробки елементів статичних масивів.

Наприклад, розрахуємо середнє арифметичне значення елементів масиву; кількість елементів масиву і їх значення користувач вводить з клавіатури.

int *x; //покажчик

 unsigned int N; //кіл-сть елементів масиву 

cout << "Zadajte kilkist elementy masyvu\n";

cin >> N; //зчитуємо кількість елементів масиву

x = new int [N]; //динамічно виділяємо пам’ять

cout << "Vvedit elementy masyvu\n";

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

 cin >> x[i]; //введення елементів масиву

 double sum = 0;

 for (i = 0; i < N; i++)

 sum +=x[i];

sum /=N;

cout << "Seredne znachennya masyvu = " << sum << endl;

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

delete[] x;


Тема 9 Багатовимірні масиви

План

9.1 Багатовимірні статичні масиви

9.2 Генерація послідовності випадкових чисел

9.3 Багатовимірні динамічні масиви

9.1 Багатовимірні статичні масиви

Багатовимірні масиви в мові С++ можуть мати не один, а декілька індексів. Оператор оголошення багатовимірного масиву має вигляд:

Тип ідентифікатор [розмірність1 ][розмірність2]…[ розмірністьN];

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

Кількість елементів багатовимірного масиву визначається добутком максимальних значень індексів.

стовпчик

стовпчик

стовпчик

стовпчик

0

1

2

3

рядок 0

a [0] [0]

a [0] [1]

a [0] [2]

a [0] [3]

рядок 1

a [1] [0]

a [1] [1]

a [1] [2]

a [1] [3]

рядок 2

a [2] [0]

a [2] [1]

a [2] [2]

a [2] [3]

Рисунок 9.1 – Двомірний масив з 3ма рядками і 4ма стовпцями

Наприклад, оператор:

int a[3][4];

оголошує масив із 3х елементів цілого типу, кожний з яких є масивом із 4х елементів цілого типу. Розмір масиву дорівнює 12.

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

Найбільше застосування в програмах, поряд з одновимірними масивами, отримали двовимірні, які часто називають матрицями чи таблицями. Перший індекс визначає кількість рядків в матриці, другий – кількість стовпчиків. В пам’яті комп’ютера елементи розміщуються рядками. Для звернення до елементу матриці вказують два індекси. Наприклад array[1][3] позначає 4й елемент 2го рядка матриці.

Багатовимірний масив подібно до одновимірного масиву може бути проїніциалізірован за допомогою списку ініціалізаторів. Першими ініціалізувалися елементи з найменшими індексами:

int аrray[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

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

//int b[4][5] = {{1, 2, 3, 4}, {5, 6, 7, 8, 9}};

int b[2][2] = {{1, 2}, {3, 4}};

Значення групуються в рядки, полягають у фігурні дужки. Таким чином, елементи b[0][0] і b[0][1] отримують початкові значення 1 і 2, а елементи b[1][0] і b[1][1] отримують початкові значення 3 і 4. Якщо початкових значень в даному рядку не вистачає для їх привласнення всім елементам рядка, то елементам рядка, що залишаються, привласнюються нульові початкові значення. Таким чином, оголошення

int b[2][2] = {{1, }, {3, 4}};

Означатиме, що b[0][0] набуває початкового значення 1, b[0][1]  набуде початкового значення 0, b[1][0] набуває початкового значення 3 і b[1][1] набуває початкового значення 4.

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

Приклад ініціалізації матриці

//визначити кількість рядків матриці с

Приклад введення матриці

//оголошення максимального значення індексів масиву

//оголошення матриці дійсних чисел з 5 рядків і 10 стовпців

//введення елементів матриці, цикл по рядкам матриці

//цикл по стовпцям матриці

Матриця – це двовимірний масив, тому для введення кожного елементу необхідно 2 цикла, які вкладені один в одного. Першим виконується внутрішній цикл. Він буде ініціалізуватися стільки разів, скільки виконується зовнішній цикл.

В прикладі зовнішній цикл зі змінною і керує зміною індексу рядка, внутрішній цикл зі змінною змінює індекс стовпчика. Кожний цикл має власну ініціалізацію, перевірку і поновлення. Зовнішній цикл повторює внутрішній цикл 5 разів і не виконує ніякої обробки. В внутрішньому циклі виконується введення значення елементу матриці з індексом і, j.

Під час виконання програми необхідно ввести 5 рядків дійсних чисел по 10 елементів в кожному стовпчику.

9.2 Генерація послідовності випадкових чисел

Для генерації послідовності випадкових чисел використовуються функції rand( ), srand( ), i time( ).Функція  time( ) повертає поточний календарний час, встановлений операційною системою. Якщо системний час не задано (як в даному прикладі), функція повертає число -1. функція srand( ) – використовується для визначення стартової точки при генерації послідовності випадкових чисел, які повертає функція rand( ).( функція srand() використовується, щоб при різних запусках програма могла використовувати різні послідовності випадкових чисел, — для цього вона повинна задавати різні вихідні числа.). Кожний раз викликаючи функцію rand( ) повертається ціле число із діапазону від 0 до 32767. застосування функції time( ) вимагає включення головного файлу time.h

#include<iostream>

#include <time.h>

using namespace std;

int main()

{

srand( (unsigned)time(NULL));

//установка початкового числа для генерації випадкових чисел

 int b[2][2] = {{1,},{3,4}};

 int a[3][4], RN;

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

{

 for (int j = 0; j < 2; j++)

  cout << b[i][j] << "\t";

cout << endl;

}cout << endl;

for (i = 0; i <3; i++)

{ for (int j = 0; j < 4; j++)

 {

  //RN = -1 + rand() % 5;

  //RN = (rand()%(50-0+1)+0);

  printf("%d\t", a[i][j] = -10+ rand() % 10);

 }

cout << endl;

}return 0;}

9.3 Багатовимірні динамічні масиви 

В динамічній(вільній) пам’яті можна розміщувати і двовимірні масиви. Так як, двовимірний масив – це одномірний масив, елементами якого є одномірні масиви. //Багатовимірний масив в C++ за своєю суттю одновимірний.

Щоб розмістити в пам’яті цілий масив з 3хрядків і 4х стовпчиків:

int (*m)[4] ; m  = new int[3][4]; або  int (*m)[4] = new int[3][4];

круглі дужки обов’язкові, так як без них оголошення int *m[4] значить, що m є масивом з 4х покажчиків на ціле, а не покажчиком на масив з 4х цілих чисел.

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

Покажчик m є змінною,що дозволяє змінювати його значення і тим самим, наприклад, пересуватися по елементам масиву.

Змінювати значення покажчика на динамічний масив треба з обережністю, щоб не забути де знаходиться початок масиву., так як покажчик, значення якого визначається при виділенні пам’яті для динамічного масиву використовується потім для звільнення з пам’яті за допомогою операції delete[] m;

//Користуватися таким масивом можна як звичайним m[1][2] = 20;

Наступна програма ілюструє сказане:

int (*m)[4] ;

m  = new int[3][4]; //або  int (*m)[4] = new int[3][4];

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

{

 for(int j = 0; j < 4; j++)

 {

  m[i][j] = i + j;

  cout << m[i][j] << '\t';

 }

 cout << "\n";

}

 delete [] m;

Результат виконання програми:

0 1 2 3

1 2 3 4

2 3 4 5


Тема 10 Рядки. Функції роботи з рядками та символами

План

10.1 Поняття,оголошення і ініціалізація рядків

10.2 Функції роботи з рядками та символами

10.3 Функції для обробки символів(одно символьні функції)

10.1 Поняття,оголошення і ініціалізація рядків

Рядок – це послідовність символів, яка зберігається в послідовно розміщених байтах пам’яті. Отже, рядок можна зберігати в масиві символів(char), для того, щоб кожний символ містився в своєму власному елементі масиву.

Можна створити масив типу char визначеного розміру, але без початкових значень:

char str[10];

Це оголошення створить рядок, який може зберігати до 10 байт, але який поки ще не проініціалізований.

char str[10] =”Hello!”;

Це оголошення створить показаний на малюнку масив і асоціює початкове значення з іменем str.(Імя масиву завжди перетворюється в його початкову адресу.). Рядок в лапках ”Hello!”; називається рядковою константою або рядковим літералом (рис. 10).

Місце зарезервоване для рядка

Рисунок 10.1 – Розміщення рядка в пам’яті

Символ «\0» є позначенням нульового символу на мові С++: це значить, що в даному байті зберігається фактичне значення 0(а не значення 48, яке є кодом ASCII цифри «0»).

Рядкові дані закінчуються нульовим байтом. Це необхідно тому, що комп’ютеру необхідний спосіб визначення кінця рядка.

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

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

char s[ ] =”Hello!”;//масив з 7 символів

Приклад 10.1 Видалення всіх входжень символів з рядка

char s[] = "Taraaumaapa!";//початковий рядок

 char c = 'a'; // символ, який треба видалити

 cout << "String = " << s << endl; //вивід рядка

 //видалення з рядка s всіх входжень символа 'a'

 int i = 0;  //індекс для початкового рядка

 int j = i;//індекс для модифікованого рядка

 //цикл модифікації початкового рядка

 while(s[i]) // поки в рядку не нульовий байт

{

 //перевірити поточний символ рядка

 if(s[i] != 'a')//якщо s[i] не рівне 'a'

  //переписати поточний символ по новому індексу j

  //і збільшити індекс на одиницю

  s[j++] = s[i];

 //збільшити індекс і на одиницю

 i++;

}

 //записати нульовий байт в кінець модимікованого рядка

s[j] = '\0';

 //вивід результату

cout << "\nResult = " << s << endl;

Результат виконання програми:

String = Taraaumaapa!

Result = Trump!

10.2 Функції роботи з рядками та символами

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

strcpy(s1,s2) – скорочено від “string copy”, копіює вміст рядка s2 в результуючий рядок s1

strcat(s1,s2) – скорочено від “string concatenation”, конкатинує(обєднує) вміст рядка s2 з кінцем рядка s1

strlen(s) – повертає довжину рядка s (не враховуючи кінцевого нуля)

strncpy(s1,s2, n) – копіює вміст рядка s2 в рядок s1, але не більше ніж n символів(не враховуючи 0).

strncat(s1,s2, n) – конкатинує вміст рядка s2 з кінцем рядка s1, копіюючи не більше ніж n символів(не враховуючи 0).

getline(name,99); - отримує ввесь введений рядок: всі введені символи доти поки не натиснута клавіша Ентер. Перший аргумент(а даному випадку name)визначає місцезнаходження, куди будуть скопійовані символи. Другий аргумент визначає максимальне число символів для копіювання(ніколи не повинне перевищувати N-1, де N – число байт, виділених для рядка).

char str[600];

 char name[100];

 char addr[200];

 char work[200];

 //Отримати від користувача три рядки

 cout << "Enter name and press ENTER:";

cin.getline(name,99);

cout << "Enter address and press ENTER:";

cin.getline(addr,199);

cout << "Enter workplace and press ENTER:";

cin.getline(work,199);

 //Побудувати результуючий рядок і надрукувати її

strcpy(str, "\nMy name is ");

strcat(str, name);

strcat(str, ", I live at ");

strcat(str, addr);

strcat(str, ",\nand I work at ");

strcat(str, work);

strcat(str, ".\n");

 cout << str;

10.3 Функції для обробки символів(одно символьні функції):

Для використання наступних функцій необхідно підключити бібліотечний файл ctype.h

isalnum() – істинне, якщо символ належить до діапазону A..Z a..z 0..9

isalpha() – істинне, якщо символ належить до діапазону A..Z a..z

isdigit() – істинне, якщо символ належить до діапазону 0..9

islower() – істинне, якщо символ належить до діапазону a..z

isupper() – істинне, якщо символ належить до діапазону A..Z

ispunct() – істинне, якщо символ належить до знаків пунктуації

isspace() – істинне, якщо символ ‘ ‘

isxdigit() – істинне, якщо символ належить до діапазону A..F a..f 0..9

tolower(с) – повертає символ с в нижньому регістрі, якщо с в верхньому регістрі, інакше повертає с в такому ж вигляді

toupper(с) – повертає символ с в верхньому регістрі, якщо с в нижньому регістрі, інакше повертає с в такому ж вигляді

Наприклад, використання функції isspace() для формування рядка без пробілів та функції tolower(), щоб вивести всі символи в нижньому регістрі:

char str[100]; //вхідний рядок

 char res[100]; //вихідний рядок

 int i, j;

 int len; //довжина вхідного рядка

cout << "Vvedit ryadok\n";

cin.getline(str,99);

len = strlen(str);

cout<<len<<endl;

j = 0;

 for (i=0;i<len;i++)

 if (!(isspace(str[i])))

 {

  res[j]=str[i];

  res[j]= tolower(res[j]);

  j++;

 }

res[j]='\0';

printf("%s\n",res);

Результат виконання програми:

Vvedite ryadok

fsdFF   FF

fsdffff


Модуль 2 Інкапсуляція та приховування інформації

Тема 11 Функції

План

11.1 Прототип, визначення і виклик функції

11.2 Форми передачі параметрів функції

11.3 Передача масиву в якості параметру функції

11.4 Вбудовані функції

11.1 Прототип, визначення і виклик функції

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

Призначення функції: повторне використовування коду, що дозволяє зменшити розміри програми; та розділення складної програми на менші, простіші задачі.

Функція – це група пов’язаних інструкцій, які виконують визначену задачу. Раз визначивши функцію ,її можна використовувати багаторазово за необхідністю.

Щоб користуватися функціями в мові С++ , необхідно виконати наступні дії:

  •  створити прототип (оголосити) функцію;
  •  визначити функцію(описати);
  •  викликати функцію.

#include<iostream>

using namespace std;

  void Vitannya(int n); // прототип функції, відсутнє повернене значення

int Kvadrat(int);// прототип функції, повернене значення типу int

int fact(int n); //прототип функції, повернене значення типу int

int main()

{

Vitannya(3); //виклик функції

for(int x = 1; x <= 10; x++)

 cout << Kvadrat(x) << " "; //виклик функції

cout << endl;

cout << "\n" << fact(5);

return 0;

}

//описання функцій

void Vitannya(int n)

{

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

 cout << "Pryvit!";

cout << "\n";

}

int Kvadrat(int y)

{

 return y*y;

}

int fact(int n)

{

    if (n == 1)

         return 1;

    else

 { cout << n << endl;

 return n * fact(n -1); }

}  

Результата виконання програми:

Pryvit!Pryvit!Pryvit!

1  4 9 16 25 36 49 64 81 100

5

4

3

2

120Press any key to continue

Прототип функції

Всі функції, що використовуються програмою повинні бути оголошені. Оголошення функції називають прототипом функції і розміщується перед головною функцією main(), синтаксис прототипу наступний:

тип  імя_функції(список типів аргументів);

Тип – це визначений тип даних, наприклад int, float, double, повідомляє який тип значення повертає функція. Якщо функція не провертає значення, використовують тип void(означае пустий тип).

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

void one(); //не приймає і нічого не повертає 

int two(int);//приймає і повертае цілочисельне значення

double three(double b, double s); //приймає два значення int і double, повертає значення типу double

double three(int b, d);//не правильно

Визначення функції

Складається з заголовка і тіла функція. Синтаксис наступний:

тип  імя_функції(список типів аргументів)

{

Оператор(и)

return значення(вираз); }  // поверненого значення має тип функції

Заголовок повинен відповідати прототипу функції. Тобто тип і ім’я функції повинні співпадати з вказаними в прототипі. Список параметрів повинен включати ті ж типи і слідувати в тому ж порядку, в якому вони вказувались в прототипі. Після типу параметру вказується ідентифікатор, який використовується в тілі функції для позначення цього параметру. Вказані в круглих дужках заголовка параметри прийнято називати формальними. Після заголовка ; не ставиться. Тіло функції складається з послідовності операторів які полягають в фігурні дужки {}. Коли інструкції відсутні, фігурні дужки все одно необхідні. Виконання функції закінчується після виконання оператору return, який вказує , що функція повертає значення(вираз). Якщо присутні декілька операторів return, то виконання функції закінчується після першого

. Якщо функція типу void, тобто не повертає значення(відповідають в Pascal процедурам), то оператор return необов’язковий ,при його відсутності закриваюча фігурна дужка означає кінець функції.

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

int Kvadrat(int y)

{ return y*y;}

int Kvadrat(int y)

{int a = y*y;

return a;}

В другому випадку оголошується локальна змінна а, яку не видно за межами функції. Інструкція return вказує, що функція повертає значення y*y типу int.

Виклик функції

Як тільки функція оголошена і описана(визначена) її можна використовувати(викликати) в будь якій кількості і з будь якої функції.

Якщо функція повертає значення, то виклик функції – це вираз, значення якого є повернений функцією результат обчислень. Цей вираз можна використовувати в операторі привласнення в якості операнда праворуч від знаку рівності(=). Якщо функція має тип void, її не можна використовувати в операторі привласнення.

Рекурсія

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

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

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

11.2 Форми передачі параметрів функції

11.2.1 Передача параметру по значенню

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

11.2.2 Передача параметру по посиланню

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

int sqrPoZkach(int);

void sqrPoPosyl(int &);

main()

{

 int x = 2, z = 4;

cout << "x = " << x << " pered sqrPoZkach " << endl

<< " Znachennya, povernene sqrPoZkach: "

<< sqrPoZkach(x) << endl

<< "x = " << x << " pislya sqrPoZkach " << endl << endl;

 

cout << "z = " << z << " pered sqrPoPosyl " << endl;

sqrPoPosyl(z);

cout << "z = " << z << " pislya sqrPoPosyl" << endl;

 return 0;

}

int sqrPoZkach(int a)

{

 return a *=a;//аргумент оператору виклику не змінюється

}

void sqrPoPosyl(int &p)

{

p *=p;// аргумент оператору виклику змінюється

}

Результат виконання програми:

x = 2 pered sqrPoZkach

Znachennya, povernene sqrPoZkach: 4

x = 2 pislya sqrPoZkach

z = 4 pered sqrPoPosyl

z = 16 pislya sqrPoPosyl

Для продолжения нажмите любую клавишу . . .

11.2.3 Параметри за замовчуванням

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

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

11.2.4 Покажчик 

Покажчик на int – це змінна, значення якої є адреса будь-якої змінної типу int.

int *p; // оголошення покажчика p на int

int a = 3; // оголошення змінної a типу  int

p = &a; //& - операція «взяття адреси». Покажчику 'p' привласнюється //адреса змінної 'а'. І з 'а' можна працювати напряму, знаючи тільки адресу його //байтів в пам'яті. Ця адреса тепер лежить у 'p'.

*p = a; //* - операція «розіменування». В пам'ять по адресу p записати 3 як  int.

Отже:

Якщо a - змінна, то &a - її адреса в пам'яті.

Якщо p - покажчик (адреса чогось), * p - значення за цією адресою, яке можна читати і змінювати.

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

Якщо,

int a; / / - змінна типу int

int & b = a; / / - посилання на a

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

void f(int &a)
{
a = 3;
}

void main()
{
int k = 4;
f(k);
printf("%d\n",k);
}

Програма виведе '3 '. Якщо в оголошенні 'f' прибрати & перед a, то програма виведе 4.

Плюс посилань - не треба користуватися розіменуванням (оператор *) і взяттям адреси (оператор &).

Мінус - обов'язково треба на що-небудь посилатися, тобто не можна передати NULL (нуль як адреса) як посилання, хоча можна передати NULL як покажчик (наприклад, для ігнорування якогось параметра).

Ось аналог цієї програми з покажчиками:

void f(int *a)
{
*a = 3;
}

void main()
{
int k = 4;
f(&k);
printf("%d\n",k);
}

Результат виконання програми:

11.3 Передача масиву в якості параметру функції

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

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

Приклад функції виведення стану одномірного масиву.

void view(const int*, int = 5);

int main()

{

 int const SIZE = 12;

 int a[SIZE] = {1, 2, 3, 4, 5, 6};

cout << "\nUsing default value\n";

view(a);

cout << " \nUsing identifier\n";

view(a, SIZE);

cout << " \nUsing constant\n";

view(a,3);

 return 0;

}

void view(const int* p, int size)

{

 int n = 5;

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

{

 for(int j = 0; j<n &&(i+j)<size; j++)

  cout << p[i+j] << '\t';

 i += n - 1;

 cout << endl;

}

}

 

Результат виконання програми:

Using default value

1       2       3       4       5

Using identifier

1       2       3       4       5

6       0       0       0       0

0       0

Using constant

1       2       3

Для продолжения нажмите любую клавишу . . .

// функція з двома аргументами

void n_chars(char c, int k);

int main()

{

 int times;

 char ch;

cout << "Vvedit symvol: ";

cin >> ch;

 while(ch != 'q')//q для виходу з програми

 {

 cout << "Vvedit cile chyslo: ";

 cin >> times;

 n_chars(ch, times);//функція з двома аргументами

 cout << "\nVvedit inshyj symvol abo natysnit 'q' dlya vyhodu: ";

 cin >> ch;

}

cout << "Kilkist symvoliv " << times << ".\n";

cout << "Byvaj\n";

 return 0;

}

void n_chars(char c, int n)// відображає значення c n разів

{

 while(n-- > 0)// продовжується поки n не дорівнює 0

 cout << c;

}

Результат виконання програми:

Vvedit symvol: f

Vvedit cile chyslo: 7

fffffff

Vvedit inshyj symvol abo natysnit 'q' dlya vyhodu: g

Vvedit cile chyslo: 2

gg

Vvedit inshyj symvol abo natysnit 'q' dlya vyhodu: q

Kilkist symvoliv 2.

Byvaj

Для продолжения нажмите любую клавишу . . .

11.3.1 Одномірний масив як параметр

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

float sum(float *a, int n);

float sum(float  a[], int n);

float sum(float  a[100], int n);

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

Масиви в функцію можна передавати ще за допомогою двох покажчиків – на перший і на "елемент", наступний за останнім елементом масиву.

float sum(float *begin, float * end);

void square(int x, int& result);

float sum(float *begin, float * end);

int main()

{

 int y = 0;   //Ініціалізація 

square(3, y);

cout << "y=" << y << endl; //y=9

 float m[100] = {1, 2, 3, 14 };

cout << sum(m,m+100) << endl;

 return 0;

}

//одномірний масив як параметр

float sum(float *begin, float * end)

{

 float s = 0;

 while(begin != end)

 s += *(begin++);

 return s;

}

void square(int x, int& result)

{

result = x*x;

}

Результат виконання програми:

y=9

20

Для продолжения нажмите любую клавишу . . .

11.3.2 Двовимірний масив як параметр

Коли параметром є двовимірний масив, його друга розмірність обов'язково вказується в оголошенні функції float a[][4]. Вона визначає розмірність тих одномірних масивів, які складають двовимірний.

Оголошення параметру як float a[][]неприпустимо.

Приклад. Визначити функцію, яка друкує двовимірний масив a[3][4] в вигляді матриці.

void print(float a[][4], int size1)

{

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

 for (int j= 0; j<4; j++)

  cout << a[i][j] << " ";

 cout << endl;

}

Заголовок може бути і таким: float sum( float (*a)[4], int size1)

float sum( float (*mas2)[4], int size)

{

 float total =0;

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

 for (int j= 0; j<4; j++)

  total +=  mas2[i][j];

 return total;

}

11.3.3 Рядок як параметр

В якості аргументу передається адрес першого символу рядка. В прототипі має використовуватися тип char *. Суттєва різниця між рядком і звичайним масивом в тому, що в рядку присутній вбудований символ ‘\0’ кінця рядка. Тому довжину рядка як параметр передавати не обов’язково. Замість цього в функції можна застосовувати цикл, для послідовної перевірки кожного символу рядка , поки цикл не досягне‘\0’.    

int str(const char* str, char ch);

int main()

{

 char m[15]= "minimum";//рядок в масиві

 char *wail = "ululate";//wail вказує на рядок

 int ms = str(m,'m');

 int us = str(wail, 'u');

cout << ms << " m symvoliv v " << m << '\n';

cout << us << " u symvoliv v " << wail << '\n';

 return 0;

}

//функція підраховує число елементів ch в рядку str

int str(const char* str, char ch)

{

 int count = 0;

 //вийти з програми, коли *str буде рівним '\0'

 while (*str)

{

 if (*str == ch)

  count++;

 //перемістити покажчик на наступний символ - char

 str++;

}

 return count;

}

Результат виконання програми:

3 m symvoliv v minimum

2 u symvoliv v ululate

Для продолжения нажмите любую клавишу . . .

11.4 Вбудовані функції

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

Приклад використання вбудованої функції:

inline float cube(const float s){return s*s*s;}

int main()

{

cout <<"Vvedit dovzhynu storony kyba:";

 float side;

cin >> side;

cout << "Ob'em kuba zi storonoyu "

 << side << " = " << cube(side)

 <<endl;

 return 0;

}

Результат виконання програми:

Vvedit dovzhynu storony kyba:3

Ob'em kuba zi storonoyu 3 = 27

Для продолжения нажмите любую клавишу . . .

Тема 12 Перевантаження функцій

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

void print(int);// друкує ціле

void print(char *);// друкує рядок

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

int min3(int x, int y, int z);

double min3(double x, double y, double z);

int main()

{

 int a = 2, b = 5, c = -5;

cout << min3(a*c, b, c)<< endl;

cout << min3(0.6*a, 10.0, 5.0)<< endl;

 return 0;

}

int min3(int x, int y, int z)

{

 int min = x;

 if(y< min) min = y;

 if(z< min) min = z;

 return min;

}

double min3(double x, double y, double z)

{

 double min = x;

 if(y< min) min = y;

 if(z< min) min = z;

 return min;

}

Результат виконання програми:

-10

1.2

Для продолжения нажмите любую клавишу . . .

Коли викликається min3() з цілочисельними значеннями(можуть бути не тільки числа, а і змінні і вирази), компілятор використовує першу функцію повертаючи ціле значення. Якщо ж використовуються аргументи типу double, викликається друга функція з тією ж назвою(тут використовується змішаний аргумент 0,03*а, int а «підтягується» до типу double, і в результаті …). Коли ж типи аргументів будуть змішаними min3(.03, 10, 5.0), компілятор не знайде потрібної функції і повідомить про помилку.

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

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


Тема 13 Шаблони функцій

План

13.1 Шаблони функцій

13.2 Перевантажені шаблони

13.1 Шаблони функцій

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

Шаблони функцій надають можливість визначати функції на основі довільних типів даних.

Наприклад: створимо шаблон для здійснення обміну значень.

template <class Any>

void Obmin(Any &a, Any &b,)

{

Any temp;

temp = a;

a = b;

b = tenp;

}

Перший рядок вказує, що встановлюється шаблон і довільному типу даних привласнюється імя Any. В описання обов’язково входить ключове слово template і class(чи typename за новим стилем підкреслює, що параметр Any є типом), а також котові дужки. Імя типу програміст вибирає на свій розсуд (в нашому випадку Any). Шаблон не створює ніяких функцій, він надає компілятору вказівки відносно визначення функції. Якщо необхідно, щоб функція виконувала обмін значеннями типу int, то компілятор створить функцію, що буде відповідати зразку шаблона, підставляючи int замість Any. Аналогічно для інших типів.

Щоб повідомити компілятору, що необхідна визначена форма функції обміну значеннями, достатньо в програмі викликати функцію Obmin(). Прототип шаблону функції розміщується в верхній частині файлу, а описання шаблону функції після main().(В старих версіях С++ вимагається, щоб прототип і визначення шаблону розміщувались в тексті програми, перед першим зверненням до шаблону,  в нових версіях надається вибір. )Приклад 2:

template <typename T>

T min3(T x, T y, T z)

{

T min =x;

 if(y < min)

 min = y;

 if(z < min)

 min = z;

 return min;

}

int main()

{

 int a = 2, b = 5, c = -5;

cout << min3(a*c, b, c)<< endl;

cout << min3(0.6*a, 10.0, 5.0)<< endl;

 return 0;

}

Результат виконання програми:

-10

1.2

Для продолжения нажмите любую клавишу . . .

13.2 Перевантажені шаблони

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

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

Приклад 3:

template<typename Any>

void Obmin(Any &a, Any &b);

template<typename Any>

void Obmin(Any *a, Any *b, int n);

void Show(int a[]);

const int Lim = 8;

int main()

{ int i = 10, j = 20;

cout << "i,j = " << i << "," << j << ".\n";

cout << "Obmin po 1 shablonu:\n";

Obmin(i,j);

cout << " Now i,j = " << i << "," << j << ".\n";

 int d1[Lim] = {0, 7, 0, 4, 1, 7, 7, 6};

 int d2[Lim] = {0, 6, 2, 0, 1, 9, 6, 9};

cout << "Oryginalni masyvy :\n";

Show(d1);

Show(d2);

Obmin(d1, d2, Lim);

cout << "mas pislya obminu :\n";

Show(d1);

Show(d2);

 return 0;}

template<typename Any>

void Obmin(Any &a, Any &b)

{ Any temp;

temp = a;

a = b;

b = temp;}

template<typename Any>

void Obmin(Any a[], Any b[], int n)

{ Any temp;

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

{  temp = a[i];

 a[i] = b[i];

 b[i] = temp; }}

void Show(int a[])

{ cout << a[0] << a[1] << "/";

cout << a[2] << a[3] << "/";

 for (int i = 4 ;i< Lim; i++)

 cout << a[i];

cout << "\n";

}

Результат виконання програми:

i,j = 10,20.

Obmin po 1 shablonu:

Now i,j = 20,10.

Oryginalni masyvy :

07/04/1776

06/20/1969

mas pislya obminu :

06/20/1969

07/04/1776

Для продолжения нажмите любую клавишу . . .

Приклад 4:

int max (int array [], int len);

long max (long array [], int len);

double max (double array [], int len); // functions

int main ()

{

int small [] = {1,24,34,22};

long medium [] = {23,245,123,1,234,2345};

double large [] = {23.0,1.4,2.456,345.5,12.0,21.0};

int lensmall = sizeof small/sizeof small [0];

int lenmedium = sizeof medium/sizeof medium [0];

int lenlarge = sizeof large/sizeof large [0];

cout << endl << max (small, lensmall);

cout << endl << max (medium, lenmedium);

cout << endl << max (large, lenlarge);

cout << endl;

return 0;

}

int max (int x [], int len)// Maximum of ints

{

int max = x [0];

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

if (max < x[i])

max = x[i];

return max;

}

long max (long x [], int len) // Maximum of longs

{

long max = x [0];

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

if (max < x[i])

max = x[i];

return max;

}

double max (double x [], int len) // Maximum of doubles

{

double max = x [0];

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

if (max < x[i])

max = x[i];

return max;

}

Результат виконання програми:

34

2345

345.5

Для продолжения нажмите любую клавишу . . .

 


Тема 14 Структури даних

План

14.1 Структура

14.2 Змінна типу структура(структурна змінна)

14.3 Ініціалізація структур

14.4 Використання покажчиків в структурах

14.5 Взаємовідношення функцій і структур

14.1 Структура

Структура  - це користувацький тип даних, який об’єднує різнотипні дані(прості змінні, покажчики, масиви, інші структури) під загальним ім’ям. Оголошення структури починається з ключового  слова struct, після якого вказується ім’я структури, далі в фігурних дужках перераховуються поля(елементи) структури. Оголошення структури закінчується крапкою з комою(;).

struct імя_структури

{

 тип_поля    імя_поля;

 ……

};

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

Оголошення структури студент

struct student

{

 char name[21];

 int age;

 int semester;

 float rating;

};

В даному прикладі структура student характеризується такими полями name[21], age, semester, rating. Ідентифікатор student  - ім’я нового типу даних.

14.2 Змінна типу структура(структурна змінна)

Змінна типу структура(структурна змінна)

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

int main()

{  student s;

 student * ps= new student;

 ….}

Для доступу до елементів структури використовують операцію крапка(.) чи операцію стрілка(->). За допомогою крапки звертаються до елементу структури по імені змінної об’єкту чи по посиланню на об’єкт. Операція стрілка забезпечує доступ до елементу структури через покажчик на об’єкт.

int main()

{

 student s= {“Petrov”, 17, 8, 5}; //ініціалізація як масив

student a;

cin >> a.name;  cin >> a.age;  cin >> a.semestr;  cin >> a.rating;

student *p = new student;

cin >> p -> name;  cin >> p -> age;  cin >> p -> semestr;  cin >> p -> rating;

student copy;

copy = a; copy = *p;

return 0;

}

14.3 Ініціалізація структур

Ініціалізація структур

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

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

Якщо елементом структури є масив, то значення всіх елементів масиву повинні бути послідовно вказані.

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

struct name

{  char prizv[15];   char imya[15];   char pobat[15];};

struct student

{  name p ;

 int age;

 int semester;

 float rating;};

int main()

{  student *p = new student;

cin >> p -> p. prizv;  cin >> p -> p. imya;   cin >> p -> p. pobat;

cin >> p -> age;  cin >> p -> semestr;  cin >> p -> rating;

student a;

cin >> a. p. prizv;  cin >> a.p. imya;   cin >> a.p. pobat;

cin >> a.age;  cin >> a.semestr;  cin >> a. rating;

return 0;}

14.4 Використання покажчиків в структурах

Використання покажчиків в структурах

При використовуванні покажчиків в структурах необхідно виділяти пам'ять для кожного створеного об’єкту.

struct student

{

 char *name;

 int age;

 int semester;

 float rating;

};

int main()

{

 student s;

s.name = new char[21];

14.5 Взаємовідношення функцій і структур

Взаємовідношення функцій і структур

Ф може повертати структуру як результат:

struct help {char *name; int number;};

help func1(void); //прототип функції

Ф може повертати покажчик на структуру:

help*  func2(void); // прототип функції

Ф може повертати посилання на структуру:

help&  func2(void); // прототип функції

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

void func4 (help str);// пряме використання

void func5 (help *ptr); // через покажчик

void func6 (help& rst); // через посилання

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

Операндом для new м.б. структурний тип. В цьому випадку виділяється память для структури даного типу, і операція new повертає покажчик на виділену память. Память м.б. виділена і для масиву структур, наприклад так:

help *p = new help[10]; // пам'ять для масиву структур

в цьому випадку операція new повертає покажчик на початок масиву.

Подальші діє з елементами масиву структур підпорядковуються правилам індексації і доступу до полів структури. Наприклад, дозволений такий доступ:

p[0].next = NULL;

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

Приклад 1 використання структури: Vvedit kil-ct studentiv: 2

struct student

{  char name[21];

 int age;

 int semestr;

 float rating;

};

void read(student & s)

{  cout << " Name: ";

 cin >> s.name;

 cout << "\n Age: ";

 cin >> s.age;

 cout << "\n semestr: ";

 cin >> s.semestr;

 cout << "\n rating: ";

 cin >> s.rating;

}

void print(student & s)

{  printf("\t%10s  \t", s.name);

 printf("\t%3d  \t", s.age);

 printf("\t%1d  \t", s.semestr);

 printf("\t%.2f \t", s.rating);

}

int main()

{ student *s;

 //student s[2];

 int n, size;

cout << "Vvedit kil-ct studentiv: ";

cin >> n;

cout << endl;

size = sizeof s * n;

// cout << size;

s = new student [size];

cout << "\nVvedit dani\n" ;

 float sr_rating = 0.0;

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

{

 read(s[i]);

 // cout >>s[i]->rating;

 sr_rating += s[i].rating;

}

sr_rating =sr_rating/n;

printf("\t\tName\t\tAge\t\tsemestr\t\trating\n");

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

{

 print(s[i]);

 cout << endl;

}

printf("Serednij bal v grupi: %.2f \n", sr_rating);

 return 0;

}

Результат виконання програми:

Vvedit dani

Name: Ivan

Age: 18

semestr: 3

rating: 1.2

Name: Kolya

Age: 18

semestr: 3

rating: 2.0

               Name            Age             semestr         rating

             Ivan               18             3               1.20

            Kolya               18             3               2.00

Serednij bal v grupi: 1.60

Для продолжения нажмите любую клавишу…

Приклад 2:

struct student

{

 char *name;

 int age;

 int semestr;

 float rating;

};

//Функція заповнення масиву структур.

void read(student & s)

{

 cout << " Name: ";

 cin >> s.name;

 cout << "\n Age: ";

 cin >> s.age;

 cout << "\n semestr: ";

 cin >> s.semestr;

 cout << "\n rating: ";

 cin >> s.rating;

}

//Функція виведення на екран

void print(student & s)

{

 printf("%10s  ", s.name);

 printf("%3d  ", s.age);

 printf("%1d  ", s.semestr);

 printf("%.2f ", s.rating);

}

int main()

{

 student s;

 s.name = new char[21];

 read(s);

 print(s);

 return 0;

}

Результат виконання програми:

Name: Franko

Age: 20

semestr: 6

rating: 4.5

   Franko   20  6  4.50 Для продолжения нажмите любую клавишу . . .


Тема 15 Поняття класу. Відділення інтерфейсу від реалізації.

План

15.1 Поняття класу.

15.2 Відділення інтерфейсу від реалізації.

15.1 Поняття класу.

ООП – передбачає представлення даних в вигляді об’єктів і класів.

В С++ програміст має можливість створювати власні типи даних і визначати операції над ними за допомогою класів.

Клас – це похідний структурований тип даних, введений програмістом на основі вже існуючих типів. Він об’єднує дані і методи їх обробки. Після визначення класу можна створити об’єкти цього класу. Об’єкт – це екземпляр одного типу; число екземплярів – довільне.

Клас визначається конструкцією:

class ім’я_класу { список компонентів; };

ключ_класу: struct, class (чи union). Структури є частковим випадком класу, компоненти структури за замовчуванням є відкритими. Але обидва ключових слова створюють клас в С++ (це означає, що загальний термін «клас» і ключове слово class не зовсім однакові). Компонентами класу можуть бути дані, функції, класи(і ін..дружні функції, дружні класи,бітові поля, перерахування, імена типів). Функції класу називають методами класу чи компонентними функціями, дані класу називають компонентними даними чи полями даних. У фігурних дужках знаходиться тіло класу. Визначення класу закінчується «;».

Область видимості в класах

Визначаючи клас всі дані і методи його за областю видимості поділяються на відкриті(public) і закриті(private):

1. public – дані і методи доступні всім методам в файлі;

2. private – дані і методи доступні тільки методам цього ж класу;

3. protected – те ж що private, відмінності з’являються в наслідувані.

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

Клас є елементом організації програми. Клас оголошується через визначення всіх його компонентів.

Приклад1.

class Time{

public:

Time();

void setTime(int, int, int);

void print24Time();

void print12Time();

private:

int hour;

int minute;

int second;};

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

Інтерфейс класу

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

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

Метод з хвилькою(тильда) на початку називається деструктором. Він викликається при руйнуванні об'єктів класу. Деструктор виконує «завершуючи службові дії» над кожним об’єктом класу перед тим, як пам'ять відведена під цей об’єкт, буде повторно використовуватися системою.

Реалізація класу

Всі оголошені методи в класі повинні бути обов’язково визначені в так званій реалізації класу. Для описання реалізації методу необхідно записати його в наступній формі :

тип_поверненого_значення    імя_класу :: ім’я_методу

:: - операція глобального дозволу

, встановлює взаємозв'язок функції і класу, до якого відноситься ця функція.

Time::Time() {hour = minute = second = 0;}

void Time :: setTime(int h, int m, int s)

{

hour = (h >= 0 && h < 24 ? h : 0 );

minute = (m >=0 && m < 60 ? m : 0 );

second = (s >=0 && s < 60 ? s : 0 );}

void Time ::print24Time()

{

cout << (hour < 10 ? "0" : "")<< hour << ":"

  << (minute < 10 ? "0" : "")<< minute << ":"

  << (second < 10 ? "0" : "")<< second;

}

void Time ::print12Time()

{

cout << ((hour == 0 || hour == 12) ? 12 : hour%12)

 << ":" << (minute < 10 ? "0" : "") << minute

 << ":" << (second < 10 ? "0" : "") << second

 << (hour < 12 ? " dnya" : " nochi");

}

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

Приклад 2:

# include <iostream>

using namespace std;

//Визначення абстрактного типу даних(АТД) Time

class Time{

private:

int hour; //0-24

int minute; //0-60

int second; //0-60

public:

//Time(): hour(0), minute(0), second(0) {} //конструктор

Time();

void setTime(int, int, int);// установка год.,хв.,сек.

void print24Time();// час по 24год.

void print12Time();// час по 12год.

friend ostream & operator<<(ostream & out, Time & t){

out << t.hour << t.minute << t.second;

 return out;

}

};

//конструктор Time привласнює нульові початкові значення

//кожному елементу даних.Забезпечує узгодженне початковий

//стан всіх об'єктів Time

/*ostream & operator<<(ostream & out, Time & t)

{

out << t.hour << t.minute << t.second;

return out;

}*/

Time::Time() {hour = minute = second = 0;}

//Time :: Time(): hour(0), minute(0), second(0) {}

//Задає нові значення Time за 24год. шкалою

//перевірка коректності даних

//обнулення неправильних даних

void Time :: setTime(int h, int m, int s)

{

hour = (h >= 0 && h < 24 ? h : 0 );

minute = (m >=0 && m < 60 ? m : 0 );

second = (s >=0 && s < 60 ? s : 0 );

}

//друк часу за 24год. шкалою

void Time ::print24Time()

{

cout << (hour < 10 ? "0" : "")<< hour << ":"

  << (minute < 10 ? "0" : "")<< minute << ":"

  << (second < 10 ? "0" : "")<< second;

}

//друк часу за 12год. шкалою

void Time ::print12Time()

{

cout << ((hour == 0 || hour == 12) ? 12 : hour%12)

 << ":" << (minute < 10 ? "0" : "") << minute

 << ":" << (second < 10 ? "0" : "") << second

 << (hour < 12 ? " dnya" : " nochi");

}

// застосування класу  Time

int main()

{

Time t;// визначення екземпляру об'єкту t класу Time

cout << "Pochatk. znach. 24h = ";

t.print24Time();

cout << endl << "Pochatk. znach. 12h = ";

t.print12Time();

t.setTime(15, 45, 3);

cout << endl << endl

 << "Znach. 24h pislya setTime = ";

t.print24Time();

cout << endl << "Znach. 12h pislya setTime = ";

t.print12Time();

// спроба встановити неправильний час

t.setTime(70, 70 , 70);

cout << endl << endl

<< "Pislya sproby nepravylnoi ustanovky: "

 << endl << "Znach. 24h :";

t.print24Time();

cout << endl << "Znach. 12h : ";

t.print12Time();

cout << endl;

cout << t;

return 0;

}

Результат виконання програми:

Pochatk. znach. 24h = 00:00:00

Pochatk. znach. 12h = 12:00:00 dnya

Znach. 24h pislya setTime = 15:45:03

Znach. 12h pislya setTime = 3:45:03 nochi

Pislya sproby nepravylnoi ustanovky:

Znach. 24h :00:00:00

Znach. 12h : 12:00:00 dnya

000Для продолжения нажмите любую клавишу . . .

15.2 Відділення інтерфейсу від реалізації.

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

Розіб’ємо програму на ряд файлів. При побудові програми на С++ кожне визначення класу зазвичай розміщується в заготовочний файл, а визначення компонентних функцій цього класу розміщуються в файл вихідних кодів з таким же  ж базовим ім’ям. Заготовочні файли включаються через #include в кожний файл, в якому використовується клас, а файл з вихідним кодом компілюється і компонується з файлом, головної програми. Нижче представлений заголовочний файл класу Time(1 з 3):

//TIME1.H

//оголошення класу Time

//функції-елементи визначені в TIME1.СРР

//запобігання багаторазового включення заголовного файлу

//#ifndef TIME1_H

#if !define TIME1_H

#define TIME1_H 0

 //вищначення абстрактного типу даних Time

class Time{

public:

Time();

void setTime(int, int, int);

void print24Time();

void print12Time();

private:

int hour;

int minute;

int second;

};

#endif 

Вихідний файл визначень функцій-елементів класу Time(2 з 3)

//TIME1.CPP

//визначення функцій-елементів для класу Time

#include <iostream>

#include "time1.h"

using namespace std;

Time::Time() {hour = minute = second = 0;}

void Time :: setTime(int h, int m, int s)

{

hour = (h >= 0 && h < 24 ? h : 0 );

minute = (m >=0 && m < 60 ? m : 0 );

second = (s >=0 && s < 60 ? s : 0 );

}

void Time ::print24Time()

{

cout << (hour < 10 ? "0" : "")<< hour << ":"

  << (minute < 10 ? "0" : "")<< minute << ":"

  << (second < 10 ? "0" : "")<< second;

}

void Time ::print12Time()

{

cout << ((hour == 0 || hour == 12) ? 12 : hour%12)

 << ":" << (minute < 10 ? "0" : "") << minute

 << ":" << (second < 10 ? "0" : "") << second

 << (hour < 12 ? " dnya" : " nochi");

}

Програма драйвер класу Time(3 з 3)

//pusk_time1.cpp

//Драйвер класу time1

//Зауваження: Компілюється разом з TIME1.CPP

#include <iostream>

#include "time1.h"

using namespace std;

//драйвер для перевірки простого класу Time

int main()

{

Time t;

cout << "Pochatk. znach. 24h = ";

t.print24Time();

cout << endl << "Pochatk. znach. 12h = ";

t.print12Time();

t.setTime(15, 45, 3);

cout << endl << endl

 << "Znach. 24h pislya setTime = ";

t.print24Time();

cout << endl << "Znach. 12h pislya setTime = ";

t.print12Time();

t.setTime(70, 70 , 70);

cout << endl << endl

<< "Pislya sproby nepravylnoi ustanovky: "

 << endl << "Znach. 24h :";

t.print24Time();

cout << endl << "Znach. 12h : ";

t.print12Time();

cout << endl;

return 0;

}

Оголошення класу полягає в умовну директиву препроцесора

#ifndef TIME1_H

//#if !define TIME1_H

#define TIME1_H

#endif    

Щоб уникнути багаторазового включення в програму заготовочного файлу.


Тема 16 Конструктор. Деструктор. Вказівник this

План

16.1 Конструктор

16.2 Деструктор

16.3 Покажчик this

16.1 Конструктор 

Особливості конструктора, які відрізняють його від інших методів:

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

Призначення основне к. – виділення памяті для даних-елементів типу покажчик та ініціалізація полів об’єкту класу.

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

- к. за замовчуванням;

- к. з параметрами;

- к. копіювання;

Жоден к. н повинен обробляти дані-елементи, за це відповідають інші методи.

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

Ініціалізація виконується за допомогою списку ініціалізації, розміщеного між двокрапкою, яке слідує за закриваючою дужкою в заголовку конструктора , і тілом к.. привласнені при ініціалізації даних значення записуються в круглих дужках після ідентифікаторів даних-елементів і  відділяються один від одного «;». Послідовність елементів не має знаення.

//Time::Time() {hour = minute = second = 0;}

Time :: Time(): hour(0), minute(0), second(0) {}

Конструктор за замовчуванням.

Конструктор за замовчуванням – це к. без параметрів. Цей к. завжди повинен бути визначений для будь-якого класу.к. за замовчуванням може не виконувати ніяких дій, але зазвичай він ініціалізує дані класу нульовими значеннями. Формат оголошення :

public: імя_класу();

наприклад, для класу CBook прототип к. за замовч. CBook();

приклад: реалізація к. за замовчуванням

CBook ::CBook() :m_year(0), m_ptitle("")

{ m_author[0] = '\0'; }

В результаті буде побудований об’єкт книга, у якого поле m_year  отримає нульове значення, масив m_author почнеться з нульового байту так як і назва книги, на яку вказує m_ptitle. Для ініціалізації автора не може бути використаний ініціалі затор, так як поле оголошене в класі як символьний масив.

Конструктор з параметрами.

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

public: імя_класу(список формальних параметрів);

Для класу CBook конструктор з параметрами має прототип: CBook(char*,char*, int);

Приклад: реалізація конструктора з параметрами:

CBook ::CBook(char* author,char* title, int year)

:m_year(year), m_ptitle(title)

{

strncpy(m_author, author, 49);

if (strlen(author)>49) m_author[49]='\0';

 

}

В результаті буде побудований об’єкт-книга, в якого поле m_year отримає значення параметру year, в масив m_author буде скопійовано не більше 49 байт з рядка author, і покажчик m_ptitle буде містити адресу, по якій скопійоване значення title.

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

Конструктор копіювання – створює копію об’єкта в ОП за допомогою іншого об’єкту тогож класу. В якості параметру цей конструктор отримує посилання на об’єкт, копія якого необхідно створити. Прототип має формат:

public: імя_класу(імя_класу&);

для CBook(CBook&);

Приклад реалізація конструктора копіювання:

CBook ::CBook(CBook& o) :m_year(o.m_year)

{

strncpy(m_author,o.m_author, strlen(o.m_author)+1);

m_ptitle = new char [strlen(o.m_ptitle)+1];

strncpy(m_ptitle,o.m_ptitle, strlen(o.m_ptitle)+1);

}

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

16.2 Деструктор

Д. автоматично викликається при знищенні об’єкту. Імя як в конструктора тільки з ~(тільдою). Д. також не повертає значення і не має аргументів(так як не можливо знищити об’єкт декількома способами). Д. – звільняє пам'ять виділену конструктором при створенні об’єкта.

На відмінну від конструктора деструктор не перевантажується и може бути в класі тільки один. Краще завжди визначати д. класу, навіть коли він не виконує дії і має пусте тіло.

public: ~імя_класу();

~CBook();

CBook ::~CBook()  {delete []m_ptitle; }

Результатом є звільнення блоку памяті з початковим адресом m_ptitle, виділеним раніше конструктором.

Виклик к. і д.

К і д викликаються автоматично при оголошенні об’єкту класу. Незалежно оголошується явно чи створюється динамічно за допомогою new.Форми виклику деструктора:

- якщо після імені об’єкту чи типу класу в операторі new нічого не вказано або стоять пусті круглі дужки, викликається конструктор за замовчуванням;

- якщо в дужках записані параметри, то викликається конструктор з параметрами;

- якщо в дужка – імя об’єкту, то викликається конструктор копіювання.

16.3 Покажчик this

Покажчик,якой позначається ключовим словом this – є неявно визначеним покажчиком на сам об’єкт і являється прихованою внутрішньою змінною класу. Звідси випливає:

  •  this – вказує на адресу активного об’єкту в оперативній пам’яті;
  •  * this – сам активний об’єкт;
  •  this →ім’я_елементу_класу – вказує на елемент активного об’єкту. Як правило this → не пишеться для активного об’єкту в методах класу, які обробляють цей елемент активного класу;
  •  this →ім’я_методу_класу(список фактичних параметрів) – викликає метод для активного об’єкту. Як правило, this → не пишеться при виклику методу активним об’єктом класу;
  •  отримати значення покажчика в методах класу можна за допомогою ключового слова this;
  •  this – це локальна змінна недоступна за межами класу. Взнати значення покажчика this  будь-якого обєкту класув функціях програми можна тільки за допомогою відкритого методу класу, який повертає це значення чи за допомогою операції “взяття адреси” - &.

Абсолютно кожний об’єкт має свій власний покажчик this. Цей покажчик не треба оголошувати в класі, так як оголошення: ім’я_класу * const this приховано в класі як оголошення покажчика на константу. Змінити this неможливо.


Тема 17 Введення/виведення значень об’єктів класу

Частиною стандартної бібліотеки C++ є бібліотека iostream - об'єктно-орієнтована ієрархія класів, де використовується і множинне, і віртуальний спадкування. У ній реалізована підтримка для файлового введення / виведення даних вбудованих типів. Крім того, розробники класів можуть розширювати цю бібліотеку для читання та запису нових типів даних.

Для використання бібліотеки iostream в програмі необхідно включити заголовний файл

# include <iostream>

Операції введення / виводу виконуються за допомогою класів istream (потоковий ввід) і ostream (потоковий вивід). Третій клас, iostream, є похідним від них і підтримує двонаправлений вводу / виводу. Для зручності в бібліотеці визначено три стандартних об'єкта-потоку:

• cin - об'єкт класу istream, відповідний стандартного вводу. У загальному випадку він дозволяє читати дані з терміналу користувача;

• cout - об'єкт класу ostream, відповідний стандартного висновку. У загальному випадку він дозволяє виводити дані на термінал користувача;

• cerr - об'єкт класу ostream, відповідний стандартного висновку для помилок. У цей потік ми направляємо повідомлення про помилки програми.

Вивід здійснюється, як правило, за допомогою переобтяженого оператора зсуву вліво (<<), а введення - за допомогою оператора зсуву вправо (>>):

Крім читання з терміналу і запису на нього, бібліотека iostream підтримує читання і запис у файли. Для цього призначені наступні класи:

  •   ifstream, похідний від istream, пов'язує введення програми з файлом;
  •  ofstream, похідний від ostream, пов'язує виведення програми з файлом;
  •  fstream, похідний від iostream, пов'язує як введення, так і виведення програми з файлом.

Щоб використати частину бібліотеки iostream, пов'язану з файловим введенням / виводом, необхідно включити до програми заголовний файл

# include <fstream>

(Файл fstream вже включає iostream, так що включати обидва файли необов'язково.) Файловий вводу / виводу підтримується тими ж операторами:

Після цього можна оголошувати об'єкти, прив'язані до файлів:

  •  для читання даних з файлу використовуються об'єкти типу ifstream (абревіатура від input file stream);
  •  для запису даних у файл використовуються об'єкти типу ofstream (output file stream).

Наприклад,

ifstream fin; / / Потік in будемо використовувати для читання

ofstream fout; / / Потік out будемо використовувати для запису

Щоб прив'язати той чи інший потік до файлу (відкрити файл для читання або для запису) використовується метод open, якому необхідно передати параметр - текстовий рядок, що містить ім'я файлу, що відкривається.

fіn.open ("input.txt");

fout.open (" output.txt ");

Після відкриття файлів і прив'язки їх до файлових потоків, працювати з файлами можна так само, як зі стандартними потоками вводу-виводу cin і cout. Наприклад, щоб вивести значення змінної x в потік fout використовуються наступна операція

fout << x;

А щоб зчитати значення змінної з потоку fin

fin >> x;

Для закриття попередніх відкритого файлу використовується метод close () без аргументів:

fin.close ();fout.close ();

Закритий файловий потік можна перевідкрити заново за допомогою методу open, прив'язавши його до того ж або іншого файлу.

При зчитуванні даних з файлу може статися ситуація досягнення кінця файлу (end of file, скорочено EOF). Після досягнення кінця файлу ніяке читання з файлу неможливо. Для того, щоб перевірити стан файлу, необхідно викликати метод eof (). Даний метод повертає true, якщо досягнуто кінець файлу або false, якщо не досягнуть. Крім того, стан файлового потоку можна перевірити, якщо просто використовувати ідентифікатор потоку в якості логічного умови:

if (fin){}

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

int d; while(fin>>d){  }

Рисунок 17.1 – Приклад запису інформації в файл


Модуль 3 Перевантаження операторів та функцій

Тема 18 Перевантаження операцій(поліморфізм)

С++ дає можливість визначати декілька функцій, з одним іменем, за умови, що у них різні вхідні параметри(кількість і/або тип) – це перевантаження функцій чи поліморфізм. Це дає можливість використовувати одне і те ж ім’я функції для виконання однакових базових операцій, навіть коли така операція застосовується до різних типів.

Перевантаження операцій(операторів) дозволяє наділяти оператори багатозначністю. Фактично багато операцій в С++  уже э перевантаженими спочатку. Наприклад, операція *, застосовуючи до адреси, дає значення, яке зберігається за цією адресою. Але, якщо застосовувати операцію * до пари чисел, отримаємо добуток цих значень. На основі кількості і типів операндів, С++ вирішує яку дію застосувати(вжити).

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

Наприклад, звичайна операція – додавання двох масивів, за допомогою циклу for має наступну компактну форму:

//по елементне додавання

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

c[i] = a[i] + b[i];

Але в С++ можна визначити клас масив, який представляє масиви і перевантажує операцію «+» таким чином, що виконати наступну дію:

// додати два об’єкта масиву

c = a + b;

Така проста форма запису операції додавання приховує її механіку і підкреслює сутність виконуваної дії, а це ще одна ціль ООП.

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

оperatorop(список_аргументів)

де ор – це символ операції, яку треба перевантажити. Наприклад, оperator+(), перевантажує операцію «+», а оperator*() – перевантажує операцію «*». Операцією ор може бути тільки повноцінна(яка визначена вже) операція С++. Нове позначення операції не можна вводити, наприклад користуватися функцією оperator @() не можна, так як немає операції @.

c = a + b;

Нехай, маємо клас masyv, а а, b, с – об’єкти цього класу.

Компілятор переконавшись, що обидва операнди (а і b) належать одному класу masyv, замінить цю операцію відповідною операторною функцією:

c = a.оperator+( b);

ця функція використовує об’єкт a неявно(оскільки він викликає метод) і об’єкт b явно(так як він передається по аргументу) для обчислення суми, яку вона повертає.

//mytime0.h - клас Time перед виконанням

//перевантаження операції

1#ifndef MYTIME0_H

2#define MYTIME0_H

3class Time

4{private:

5 int hours;

6 int minutes;

7public:

8 Time();

9 Time(int h, int = 0);

10 void AddMin(int m);

11 void AddHr(int h);

12 void Reset(int h, int = 0);

13 Time Sum(const Time & t)const;

14 void Show() const; };

15#endif

//mytime0.cpp -реалізація методів класу Time

16#include<iostream>

17using namespace std;

18#include "mytime0.h"

19 Time::Time():hours(0),minutes(0){};

20 Time::Time(int h, int m)

:hours(h),minutes(m){};

21 void Time::AddMin(int m)

22 {

23  minutes += m;

24  hours+= minutes / 60;

25  minutes %= 60;

26 }

27 void Time::AddHr(int h)

28 {

29  hours += h;

30 }

31 void Time::Reset(int h, int m)

32 {

33  minutes = m;

34  hours = h;

35 }

36 Time Time::Sum(const Time & t)const

37 {

38  Time sum;

39  sum.minutes = minutes + t.minutes;

40  sum.hours = hours + t.hours + sum.minutes / 60;

41  sum.minutes %= 60;

42  return sum;

43 }

44 void Time::Show() const

45 {

46  cout << hours << " hours," << minutes <<" minutes";

47  cout << '\n'; }

//usetime0.cpp - використовує перший варіант

//класу Time компілює файл usetime0.cpp і

// mytime0.cpp в один програмний модуль

49#include <iostream>

50#include "mytime0.h"

51using namespace std;

52int main()

53{ Time A;

55 Time B(5, 40);

56 Time C(2, 55);

57 cout << " A = ";

58 A.Show();

59 cout << " B = ";

60 B.Show();

61 cout << " C = ";

62 C.Show();

63 A = B.Sum(C);

64 cout << " B.Sum = ";

65 A.Show();

66 return 0; }

Результат виконання роботи:

Обмеження при виконанні перевантаження.Таким же чином можна перевантажувати більшість операцій С++(табл. 18.1)

Таблиця 18.1 – Операції  які можна перевантажувати

+          -         *          /         %         ^         &         |

~         !         =         <         >         +=         -=         *=

/=         %=         ^=         &=         I=         <<         >>         >>=

<<=         ==         !=         <=         >=         &&         ||         ++

--         ->*         ,->         []         ()         new         delete

Обмеження, які С++ накладає на перевантаження операцій, визначених користувачем, детальніше:

  1.  Перевантажена операція повинна мати хоча б один операнд з типом, визначеним користувачем, - для того щоб перешкодити перевантаженню операції над стандартними типами даних.
  2.  Не можна перевизначити операцію віднімання, так щоб замість віднімання двох значень типу double виконувалась їх сума. Це обмеження забезпечує надійну роботу програми(але обмежує творчий підхід до використання системних ресурсів).
  3.  Операцію не можна застосовувати так, щоб вона порушувала правила синтаксису, яким підпорядковується вихідна операція. Наприклад, неможна перевантажити операцію ділення по модулю(%) так, щоб її можна було використовувати з одним операндом:

int x;

Time d;

% x; //недопустимо для операції ділення по модулю

% D; // недопустимо для перевантаженої операції

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

  1.  неможна створювати нові символи операцій. Наприклад, не можна зробити так, щоб функція operator** позначала зведення в квадрат (табл.. 18.2).

.         операція  приналежності

.*         операція –покажчик на елемент класу

::         операція визначення діапазону доступу

?:         умовна операція

Sizeof     

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

Таблиця 18.2 – Перевантаження операторів тільки в функції - елементи:

=   оператор привласнення

()   оператор виклику функції

[]   оператор індексації

->   доступ до елементу класу через покажчик


Тема 19 Перевантаження бінарних операторів

Тема 20 Перевантаження унарних операторів

План

20.1 Додамо операцію додавання в клас Time

20.2 Інші перевантажені операції

20.3 Використання дружніх структур

20.4 Створення дружніх конструкцій

20.1 Додамо операцію додавання в клас Time

Перетворити клас Time так, щоб можна було користуватися перевантаженою операцією додавання легко: для цього треба тільки поміняти ім’я Sum() на ім’я operator+().

На подобі функції Sum(), operator+() викликається об’єктом класу Time, приймає другий об’єкт Time в якості аргументу і повертає об’єкт Time. Таким чином можна викликати метод operator+(), використовуючи такий же синтаксис, як і для функції Sum():

A = B.operator+(C);//функціональна форма запису

Привласнюючи цьому методу ім’я operator+(), можна користуватися операторною формою запису:

A = B + C;// операторна форма запису

Цей вираз забезпечує виклик методу operator+(). Зверніть увагу, що об’єкт в лівій частині операції(це В) викликає, а об’єкт в правій частині(це С) – передається в якості аргументу.

В файлі mytime0.h замість 13рядка:

Time operator+(const Time & t)const;

В файлі mytime0.cpp замість 36рядка:

Time Time::operator+(const Time & t)const

В файлі usetime0.cpp замість 63-65рядків:

A = B.operator+(C);

cout << " A = B.operator+(C) = ";

A.Show();

B = A + C;

cout << " A + C = ";

Результат виконання даного коду:

Те що ми дали функції ім’я operator+ забезпечує її виклик при використанні як функціональної так і операторної форми запису. Компілятор приймає рішення на основі аналізу типів операндів:

int a, b, c;

Time A,B,C;

c = a + b;//виконати додавання цілочисельних величин

C = A + B;//виконати додавання, визначене для об’єктів класу Time

20.2 Інші перевантажені операції

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

В оголошення класу треба додати наступні прототипи:

Time operator-(const Time & t)const;

Time operator*(double n)const;

В файл реалізації треба дописати визначення нових методів наступним чином:

Time Time:: operator-(const Time & t)const

{

 Time diff;

 int totl1, totl2;

 totl1 = t.minutes + 60 * t.hours;

 totl2 = minutes + 60 * hours;

 diff.minutes = (totl2 - totl1) % 60;

 diff.hours = (totl2 - totl1) / 60;

 return diff;

}

Time Time:: operator*(double mult)const

{

 Time result;

 long totalminutes = hours * mult * 60 + minutes * mult;

 result.hours = totalminutes / 60;

 result.minutes = totalminutes % 60;

 return result;

}

Після цих змін можна перевірити ці методи (визначення) за допомогою програмного коду в файлі usetime0.cpp:

A = B + C; // operator+( )

cout << " B + C = ";

A.Show();

A = B - C; // operator-( )

cout << " B - C = ";

A.Show();

A = B * 2.75; // operator*( )

cout << " B* 2.75 = ";

A.Show();

Результат виконання програми:

20.3 Використання дружніх структур

В С++ існує форма доступу – дружні структури. Це:

- дружні функції;

- дружні класи;

- дружні функції – елементи.

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

Досить часто перевантаження бінарної операції класу породжує необхідність в дружніх структурах. Наприклад, множення об’єкту Time на дійсне число.

В прикладі з класом  Time перевантажена операція множення відрізняється від інших двох перевантажених операцій  тим, що вона працює двома різними типами(Time і double), а це обмежує область застосування подібної операції. Оскільки лівий операнд – викликає об’єкт:

A = B * 2.75,

перетворюється в наступне звернення до функції-елементу:

A = B.operator*(2.75);

Але як щодо оператору:

A = 2.75 * B // – не відповідає функції-елементу, оскільки 2.75 не є об’єктом класу Time

(компілятор не може замінити цей вираз зверненням до функції-елементу).

Функція, яка не є елементом класу, не викликається об’єктом

Компілятор може спів ставити цей вираз з наступним викликом функції, яка не є методом класу:

A = operator*(2.75, B);

Це функція буде мати прототип:

Time operator*( double m, const Time & t)const;

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

20.4 Створення дружніх конструкцій

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

friend Time operator*( double m, const Time & t)const; //розміщується в інтерфейс класув

Особливості цього прототипу:

  •  не дивлячись на те, що функція operator*( ) оголошена в інтерфейсі класу, вона не є функцією-елементом;
  •  хоча operator*( ) не є функцією-елементом, вона отримує ті ж права доступу, що і функція-елемент.

Потім, треба визначити функцію. Оскільки, така функція не є функцією-елементом, не треба використовувати специфікатор доступу Time::. Не можна користуватися у визначені ключовим словом friend.

Time operator*( double mult, const Time & t)const

{

Time result;

 long totalminutes = hours * mult * 60 + minutes * mult;

 result.hours = totalminutes / 60;

 result.minutes = totalminutes % 60;

 return result;

}

При наявності такого оголошення оператор:

A = 2.75 * B

Перетвориться в :

A = operator*(2.75, B);

І при цьому викликається дружня функція, не метод класу.

Дружня функція по відношенню до класу, не є її елементом, але має ті ж права доступу, що і функції-елементи.


Tема 21 Дружні функції. Перевантаження потоків введення/виведення. Робота з файлами

План

21.1 Дружні функції

21.2 Перевантаження потоків введення/виведення.

21.3 Робота з файлами.

21.1 Дружні функції

Мета перевантажених операцій – забезпечити такі ж короткі вирази в операціях для типів, визначених користувачем, якими С++ забезпечив вбудовані типи.

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

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

Як правило, операторна функція повертає об’єкт класу чи посилання на об’єкт класу, з яким вона працює.

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

Обмеження:

Операції [ ],( ), ->, = можуть бути перевантажені тільки функціями-елементами класу.

Що краще функція-елемент чи дружня функція?

[операторні функції-елементи викликаються тільки в випадку, коли лівий операнд бінарної операції чи єдиний операнд унарної операції є об’єктом того ж класу, елементом якого є функція.]

Якщо операторна-функція реалізована як функція-елемент, крайній лівий (чи єдиний) операнд повинен бути об’єктом того класу(чи посиланням на об’єкт того класу),елементом якого є функція.

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

Перевантажена операція << повинна мати лівий операнд типу ostream&(такий, як  cout в виразі cout <<classObject), так що вона не може бути функцією-елементом. Аналогічно, перевантажена операція  >> повинна мати лівий операнд типу istream& ( такий, як cin в виразі cin >>classObject), так що вона також не може бути функцією-елементом. Але кожна з цих перевантажених операторних функцій може вимагати доступ до закритих елементів даних об’єкту класу, тому такі перевантажені операторні функції роблять іноді дружніми функціями.

21.2 Перевантаження потоків введення/виведення.

Щоб виконувати операції введення і виведення користувацьких типів(, визначених користувачем), треба перевантажити операції «помістити в потік» і «взяти з потоку».

Для введення/виведення об’єктів класу використовують операторні функції operator<< “помістити в потік” operator>> “взяти з потоку”.

Time t;

cout << t;

використовують два об’єкти :

cout – об’єкт класу ostream, лівий операнд  ;

t – об’єкт класу Time, правий операнд;

Оскільки cout не належить Time, треба застосовувати дружні функції по відношенню до Time(а не до ostream, щоб не змінювати вміст класу ostream).

Функція operator<< приймає аргумент ostream і аргумент Time, тому по ідеї функція повинна бути дружньою до двох класів. Але по коду функція виконує доступ до окремих елементів об’єкту Time, об’єкт ostream використовується як єдине ціле.

ostream& operator<<(ostream& os, const Time& t)

{

 os << t.hours << " hours," << t.minutes <<" minutes";

 cout << '\n';

 return os;

}

В якості першого аргументу використовується посилання os на клас ostream. Зазвичай  os посилається на об’єкт cout, так як в  cout << t; але може посилатися і на інші визначені об’єкти класу ostream.

Об’єкт класу Time може бути переданий по значенню і по посиланню. (Обидві форми доступні для даної функції.) для передачі по посиланню необхідно менше памяті і часу ніж при передачі по посиланню.

Повернений тип ostream& - функція повертає посилання на об’єкт класу ostream, який їй був переданий.(іншими словами повернене значення операторної функції співпадає з преданим їй об’єктом напр., cout<<t повертає cout)(Це дозволяє виводити декілька об’єктів(множинний вивід) - cout << t<< t1<<...;).

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

ostream& operator<<(ostream& os, const c_name & obj)

{os << …//відображення вмісту обєкту

return os;}

Приклад перевантаження потоків введення/виведення:

#include <iostream>

#include <fstream>

using namespace std;

class Time

{

private:

 

public:int minutes;

 int hours;

Time();

Time(int h, int = 0);

 void AddMin(int m);

 void AddHr(int h);

 void Reset(int h, int = 0);

Time operator+(const Time & t)const;

Time operator-(const Time & t)const;

Time operator*(double n)const;

 friend Time operator*(double m, const Time& t)

{return t*m;}

 friend ostream& operator<<(ostream& os, const Time& t)

{

 os << t.hours << " hours " << t.minutes <<" minutes,";

 cout << '\n';

 return os;

}

 friend istream& operator>>(istream& is,  Time& t)

{

 is >> t.hours >>t.minutes;

 /*int h(0),m(0);

 is>>h>>m;

 t=Time(h,m);*/

 return is;

}

};

Time::Time():hours(0),minutes(0){};

Time::Time(int h, int m):hours(h),minutes(m){};

 void Time::AddMin(int m)

{ minutes += m;

 hours+= minutes / 60;

 minutes %= 60;

}

 void Time::AddHr(int h)

{ hours += h;

}

 void Time::Reset(int h, int m)

{ minutes = m;

 hours = h;

}

Time Time::operator+(const Time & t)const

{ Time sum;

 sum.minutes = minutes + t.minutes;

 sum.hours = hours + t.hours + sum.minutes / 60;

 sum.minutes %= 60;

 return sum;

}

Time Time:: operator-(const Time & t)const

{ Time diff;

 int totl1, totl2;

 totl1 = t.minutes + 60 * t.hours;

 totl2 = minutes + 60 * hours;

 diff.minutes = (totl2 - totl1) % 60;

 diff.hours = (totl2 - totl1) / 60;

 return diff;

}

Time Time:: operator*(double mult)const

{ Time result;

 long totalminutes = hours * mult * 60 + minutes * mult;

 result.hours = totalminutes / 60;

 result.minutes = totalminutes % 60;

 return result;

}

int main()

{ Time A;

Time B(5, 40);

Time C(2, 55);

cout << " A, B and C: " << endl;

cout << A << B << C << endl; // friend operator<<( )

A = B + C; // operator+( )

cout << " B + C = ";

cout << A; // friend operator<<( )

A = B - C; // operator-( )

cout << " B - C = ";

cout << A; // friend operator<<( )

A = B * 2.75; // operator*( )

cout << " B * 2.75 = ";

cout << A; // friend operator<<( )

A = 2.75 * B; // friend operator*( )

cout << " 2.75 * B = ";

cout << A; // friend operator<<( )

 // friend operator<<( ) and friend operator*( )

cout << " 10 * B:" << 10 * B << endl;

Time t;

 char ch;

ofstream fout;

fout.open("data.txt");

fout<<A<<B<<C;

fout.close();

ifstream fin;

fin.open("data.txt");

cout<<"\nVmist fajlu:\n";

fin>>A>>B>>C;

cout<<A<<B<<C;

fin.close();

 return 0;

}

Результат виконання програми:

A, B and C:

0 hours 0 minutes,

5 hours 40 minutes,

2 hours 55 minutes,

B + C = 8 hours 35 minutes,

B - C = 2 hours 45 minutes,

B * 2.75 = 15 hours 35 minutes,

2.75 * B = 15 hours 35 minutes,

10 * B:56 hours 40 minutes,

Vmist fajlu:

15 hours 35 minutes,

5 hours 40 minutes,

2 hours 55 minutes,

Для продолжения нажмите любую клавишу . . .          

21.3 Робота з файлами.

Оператор індексації[ ] – бінарний, операторна функція повинна мати параметр, який задає індекс.

class C

{

public:

int* p_m; int m_n;

C(int n = 10):m_n(n)

{

 p_m = new int [m_n];

 for(int i = 0;i<m_n;i++)p_m[i]=0;

}

C(C& o): m_n(o.m_n)

{  p_m = new int [m_n];

 for(int i = 0;i<m_n;i++)p_m[i]=o.p_m[i];  }

~C(){delete[]p_m;}

int& operator[](const unsigned int i)const

{return p_m[i];}

C& operator=(C& right)

{

 if(m_n!=right.m_n)

 {

  delete[]p_m;

  m_n=right.m_n;

  p_m=new int[m_n];

 }

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

  p_m[i]=right.p_m[i];

 return *this; }};

#include<iostream>

using namespace std;

void view(C&);

int main()

{ C obj(4);

for(int i=0;i<obj.m_n;i++)

 obj[i]=(i+1)*2;

cout<< "\nobj:\t\t";

view(obj);

C* p=new C(6);

cout<< "\n*p:\t\t";

view(*p);

*p=obj;

cout<< "\n*p=obj:\t\t";

view(*p);

C*pCopy=new C(*p);

cout<< "\nCOPY *p:\t\t";

view(*pCopy);

C x,y;

x=y=obj;

cout <<"\nx=y=obj\n";

cout <<"\nx:\t\t"; view(x);

cout <<"\ny:\t\t"; view(y);

delete p;delete pCopy;

return 0;}

void view(C& o)

{ for(int i=0;i<o.m_n;i++)

 cout<<o[i]<<'\t';

cout <<endl;}

Результат виконання роботи:


Тема 22 Шаблони класів

Шаблон класу є заготівлею, з якої створюються конкретні класи в процесі створення (сутності) об’єкту. Робиться це шляхом підстановки конкретного типу як аргумент. Шаблон класу ще називають параметризованим типом.

Синтаксис класу-шаблону має наступний вигляд:

template < параметри  > // оголошення класу

class     імя_класу

{   // визначення класу

};

Префікс template < параметри  > показує компілятору, що наступне визначення класу є шаблоном, а не звичайним класом. Параметри шаблону оголошуються як

class ім’я

чи

typename ім’я

в якості імені застосовують будь-який дозволений ідентифікатор. Всередині класу такий параметр може з’являтися на місцях, де дозволено писати конкретний тип.

Наприклад,створимо шаблон класу –пари з двома елементами різного типу,його можна визначити різними способами:

template < class Т1, class Т2 >

class     Раіr { T1 first; T2 second;};

template < typename Т1, typename Т2 >

class     Раіr { T1 first; T2 second;};

template < class Т1, typename Т >

class     Раіr { T1 first; T second;};

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

template < typename Т1, typename Т2 >

Визначення об’єктів для деякого класу-шаблона  має загальний вигляд:

імя_класу_шаблону < аргументи > ім’я_об’єкту; //один об’єкт

імя_класу_шаблону < аргументи > ім’я_об’єкту[кількість]; //масив об’єктів

імя_класу_шаблону < аргументи > *ім’я_об’єкту; //покажчик

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

Раіr< int, int > x;

Раіr< int, double > y;

Раіr< string, date > d;

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

Раіr< int, int > x;

Створюється об’єкт х в якому зберігаються числа типу int. Компілятор резервує область пам’яті для даних цього об’єкту, використовуючи тип int, де з’являється аргументи Т1, Т2 в інтерфейсі класу.

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

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

template<typename T>

Masyv<T>::Masyv(int n)

{ mas =new T[n];

size = n;

}

Вираз template<typename T> повинне передувати не тільки визначенню класу, а й кожному визначенню методу поза класом.

#include<iostream>

using namespace std;

template<typename T>

class Masyv

{

T* mas;

 int size;

 public:

 Masyv(int);

 ~Masyv() {delete [] mas;}

 T& operator[](int i)

 {return mas[i];}

};

template<typename T>

Masyv<T>::Masyv(int n)

{

mas =new T[n];

size = n;

}

int main()

{

Masyv<int> x(10);

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

{

 x[i]=i*i;

 cout<<x[i]<<"  ";

}

cout<<endl;

Masyv<char> c(5);

 for(int j= 0; j<5; ++j)

{

 c[j] = j+'a'; //j+97

 cout<<c[j]<<"  ";

}

 /*char k=109;//k='m';

cout<< k;//m*/

 return 0;

}

Результат виконання програми:

0  1  4  9  16  25  36  49  64  81

a  b  c  d  e  Для продолжения нажмите любую клавишу . . .


Модуль 4 Успадкування та повторне використання коду. Множинне успадкування

Тема 2.3 Успадкування

План

23.1 Рівні доступу

23.2 Захищені елементи

23.3 Вирішення конфлікту імен

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

Об’єкти різних класів і самі класи можуть знаходитися в відношенні (успадкування)наслідування, в відповідності з яким формується ієрархія об’єктів і ієрархія класів відповідно.

Механізм успадкування дозволяє визначати нові класи на основі уже існуючих. Клас, на основі якого створюється новий клас, називають базовим(батьківським) класом, а новий – похідним(дочірнім) класом. Дочірній клас отримує поля і методи базового класу. (Успадкування дозволяє «винести за дужки» те спільне, що властиве декільком класам, загальні властивості визначаються в базовому класі, а відмінності – в похідних.)

(Безпосереднім базовим класом називається такий клас, який входить в список базових класів при визначенні класу.)

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

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

23.1 Рівні доступу (Управління доступом похідних класів)

При успадкуванні важливу роль відіграє рівень(статус) доступу до компонентів класу. (В ієрархії класів існують наступні права доступу до компонентів класу:) Будь-який елемент класу має один із 3х рівнів доступу:

- закриті(private) компоненти доступні тільки всередині того класу, де вони визначені і дружнім функціям;

- захищені(protected) методи і елементи даних доступні всередині класу, в якому вони визначені, і в усіх похідних класах;

- загальнодоступні(public) компоненти класу доступні з будь-якої точки програми.

(Таким чином, для об’єкту, який обмінюється повідомленнями з іншими об’єктами і обробляє їх, доступними є:

  •  загальнодоступні компоненти всіх об’єктів програми;
  •  захищені дані і методи об’єктів, які представляють базовий клас;
  •  власні компоненти об’єктів.

При описанні класу можна змінювати статус доступу до похідних компонентів класу за допомогою модифікатора статусу  доступу. Формат описання похідного класу виглядає наступним чином:

class | struct імя_похідного_класу: [модифікатор] імя_базового_класу {компоненти_класу}; )

В заголовку похідного класу вказуються імена всіх базових класів разом зі специфікаторами доступу:

class імя_похідного_класу: специфікатор_успадкування імя_базового_класу1 [, специфікатор_успадкування імя_базового_класу2, ...] {компоненти_класу};

в якості специфікатора_успадкування використовуються ключові слова private, protected, public. Явно вказувати специфікатор доступу при успадкуванні не обов’язково. За замовчуванням приймається  private для базових класів, і public для базових структур і об’єднань.

В таблиці проілюстровані характеристики доступу до членів базового класу з похідного при різних специфікатор_успадкування.

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

Таблиця 24.1 – Специфікатори доступу в похідному класі до компонентів базового класу

Специфікатор успадкування

Специфікатор елементу базового класу

public

protected

private

public

в похідному класі public

в похідному класі protected

в похідному класі невидимі

protected

в похідному класі protected

private

в похідному класі private

в похідному класі private

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

Приклад базового і похідного класів:Date I Birthday

class Date{

private:

int day, month, year;

public:

Date(int, int, int);

Date(char*);

Date( );};

Class Birthday: public Date{

Public:

Char* name;};

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

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

Для ініціалізації елементів похідного класу ваша програма повинна викликати конструктори базової та похідного класів.

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

На додаток до загальних (public) (доступним всім) і приватним (private) (доступним методам класу) елементів C + + надає захищені (protected) елементи, які доступні базового та похідного класів.

Для вирішення конфлікту імен між елементами базової та похідного класів ваша програма може використовувати оператор глобального дозволу(::). ючи перед ним ім'я базового або похідного класу.

Просте наслідування

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

class employee

{
public:
   employee(char *, char *, float);
   void show_employee(void);
private:
   char name[64];
   char position[64];
   float salary;
};

Далі припустимо, що вашій програмі потрібно клас manager, який додає наступні елементи даних в клас employee:

float annual_bonus;
char company_car[64];
int stock_options;

У даному випадку ваша програма може вибрати два варіанти: по-перше, програма може створити новий клас manager, який дублює багато елементів класу employee, або програма може породити клас типу manager з базового класу employee.

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

class manager : public employee

{
public:
   manager(char *, char *, char *, float, float, int);
   void show_manager(void);
private:
   float annual_bonus;
   char company_car[64];
   int stock_options;
};

Ключове слово public, яке передує ім'я класу employee, вказує, що загальні (public) елементи класу employee також є загальними і в класі manager. Наприклад, наступні оператори породжують клас manager.

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

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

Наступна програма MGR_EMP.CPP ілюструє використання успадкування в C + +, створюючи клас manager з базового класу employee:

// Nasledov_empleer.cpp: определяет точку входа для консольного приложения.

//

#include "stdafx.h"

#include <iostream>

using namespace std;

#include <string.h> 

class employee

{

public:

  employee(char *, char *, float);

  void show_employee(void);

private:

  char name [ 64 ]; //ім'я і прізвище працівника

  char position[64]; //посада

  float salary; // оклад

};

employee::employee(char *name, char *position,float salary)

{

  strcpy(employee::name, name);

  strcpy(employee::position, position);

  employee::salary = salary;

}

void employee::show_employee(void)

{

  cout << "Imya: " << name << endl;

  cout << "Posada: " << position << endl;

  cout << "Oklad: $" << salary << endl;

}

class manager : public employee

{

public:

  manager(char *, char *, char *, float, float); //, int

  void show_manager(void);

private:

  float annual_bonus; //премія

  char company_car[64]; //машина компанії

};

manager::manager(char *name, char *position, char *company_car, float salary, float bonus) : employee(name, position, salary)

{

  strcpy(manager::company_car, company_car) ;

  manager::annual_bonus = bonus ;

}

void manager::show_manager(void)

{

  show_employee();

  cout << "Mashyna firmy: " << company_car << endl;

  cout << "Shorichna premiya: $" << annual_bonus << endl;

}

void main()

{

  employee worker("Ivan Ivanov", "Programist", 35000);

  manager boss("Ivan Kozlov", "Dyrektor", "Lexus", 50000.0, 5000);//, 1000);

  worker.show_employee() ;

  boss.show_manager();

}

Результат виконання програми:

Як бачите, програма визначає базовий клас employee, а потім визначає похідний клас manager. Зверніть увагу на конструктор manager. Коли ви породжує клас з базового класу, конструктор похідного класу повинен викликати конструктор базового класу. Щоб викликати конструктор базового класу, помістіть двокрапка відразу ж після конструктора похідного класу, а потім вкажіть ім'я конструктора базового класу з необхідними параметрами:

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) : employee(name, position, salary) // Конструктор базового класса

{
strcpy(manager::company_car, company_car);
manager::annual_bonus = bonus;
//manager::stock_options = stock_options;
}

Також зверніть увагу, що функція show_manager викликає функцію show_employee, яка є елементом класу employee. Оскільки клас manager є похідним класу employee, клас manager може звертатися до загальних елементів класу employee, як якщо б всі ці елементи були визначені всередині класу manager

23.2 Захищені елементи

При вивченні визначень базових класів ви можете зустріти елементи, оголошені як public, private і protected (загальні, приватні і захищені). Як ви знаєте, похідний клас може звертатися до загальних елементів базового класу, як ніби вони визначені у похідному класі. З іншого боку, похідний клас не може звертатися до приватних елементів базового класу напряму. Замість цього для звернення до таких елементів похідний клас повинен використовувати інтерфейсні функції. Захищені елементи базового класу займають проміжне положення між приватними і спільними. Якщо елемент є захищеним, об'єкти похідного класу можуть звертатися до нього, як ніби він є загальним. Для решти вашої програми захищені елементи є як би приватними. Єдиний спосіб, за допомогою якого ваші програми можуть звертатися до захищених елементів, полягає у використанні інтерфейсних функцій. Наступне визначення класу book використовує мітку protected, щоб дозволити класами, похідним від класу book, звертатися до елементів title, author і pages безпосередньо, використовуючи оператор точку:

class book

{
public:
   book(char *, char *, int) ;
   void show_book(void) ;
protected:
   char title [64];
   char author[64];
   int pages;
};

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

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

23.3 Вирішення конфлікту імен

Якщо ви породжує один клас з іншого, можливі ситуації, коли ім'я елемента класу в похідному класі є таким же, як ім'я елемента в базовому класі. Якщо виник такий конфлікт, C + + завжди використовує елементи похідного класу усередині функцій похідного класу. Наприклад, припустимо, що класи book і library_card використовують елемент price. У разі класу book елемент price відповідає продажній ціні книги, наприклад $ 22.95. У разі класу library'_card price може включати бібліотечну знижку, наприклад $ 18.50. Якщо у вашому вихідному тексті не зазначено явно (за допомогою оператора глобального дозволу), функції класу library_card будуть використовувати елементи похідного класу (library_card). Якщо ж функцій класу library_card необхідно звертатися до елементу price базового класу (book), вони повинні використовувати ім'я класу book і оператор дозволу, наприклад book:: price. Припустимо, що функції show_card необхідно вивести обидві ціни. Тоді вона повинна використовувати наступні оператори:

cout << "Библиотечная цена: $" << price << endl;
cout << "Продажная цена: $" << book::price << endl;


Тема 24 Ієрархія класів

При використанні успадкування в C + + для породження одного класу з іншого можливі ситуації, коли ви породжує свій клас з класу, який вже, у свою чергу, є похідним від деякого базового класу. Наприклад, припустимо, вам необхідно використовувати клас сотputer базовий для породження класу workstation, як показано нижче:

class work_station : public computer

{
public:
   work_station (char *operating_system, char *name, int hard_disk, float floppy, char *screen, long colors, int x_res, int       y_res, int processor, int speed, int RAM);
   void show_work_station(void);
private:
   char operating_system[64];
};

Конструктор класу workstation просто викликає конструктор класу computer, який у свою чергу викликає конструктори класів сотрuter_screen і mother_board:

work_station::work_station( char *operating_system, char *name, int hard_disk, float floppy, char *screen, long colors, int    x_res, int y_res, int processor, int speed, int RAM) : computer (name, hard_disk, floppy, screen, colors, x_res, y_res,    processor, speed, RAM)

{
   strcpy(work_station::operating_system, operating_system);
}

У даному випадку клас computer виступає в ролі базового класу. Однак ви знаєте, що клас computer був породжений з класів computer_screen і mother_board. В результаті клас work_station успадковує характеристики всіх трьох класів. На рис. 27 показано, що породження класів призводить до ієрархії класів.

Коли ваші програми будуть інтенсивно використовувати спадкування, ваша ієрархія класів, а отже, і кількість наслідуваних елементів може стати досить довгою.

Що вам необхідно знати

Множинне спадкування являє собою можливість породжувати клас з декількох базових класів. При використанні множинного спадкоємства похідний клас отримує характеристики (елементи) існуючих базових класів. Підтримка множинного спадкоємства в C + + надає вашим програмам величезні можливості об'єктно-орієнтованого програмування

Множинне спадкування є здатністю породженого класу успадковувати характеристики декількох базових класів.

Для породження класу з декількох базових після імені нового класу і двокрапки ви вказуєте імена базових класів, розділяючи їх комами, наприклад class cabbit: public cat, public rabbit.

При визначенні конструктора похідного класу ви повинні викликати конструктори всіх базових класів, передаючи їм необхідні параметри.

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


Тема 25 Віртуальні функції. Абстрактні класи. Поліморфізм

План

25.1 Віртуальна функція

25.2 Чисто віртуальна функція

25.3 Абстрактний класс

25.4 Поліморфізм

25.1 Віртуальна функція

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

virtual тип_функції ім’я_функції(список параметрів);

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

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

Віртуальна функція:

  •  може бути тільки нестатична компонентна функція класу;
  •  не може бути глобальна функція;
  •  може бути оголошена дружньою.

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

1)

#include<iostream>

using namespace std;

class base

{

public:

 int i;

base(int x){i=x;}

 virtual void func()

{

 cout<<"Vykonannya funkcii func() bazovogo klasu:";

 cout << i <<'\n';

}

};

class p1:public base

{

public:

p1(int x):base(x){}

 void func()

{

 cout<<"Vykonannya funkcii func() klasu p1:";

 cout << i*i <<'\n';

}

};

class p2:public base

{

public:

p2(int x):base(x){}

 void func()

{

 cout<<"Vykonannya funkcii func() klasu p2:";

 cout << i+i <<'\n';

}

};

int main()

{ base *p;

base obj(10);

p1 obj1(10);

p2 obj2(10);

 

p=&obj;

p->func();

p=&obj1;

p->func();

p=&obj2;

p->func();

 return 0;

Результат виконання програми:

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

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

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

По-третє перевантажена функція-елемент вибирається на етапі компіляції на основі сигнатури. Віртуальна функція вибирається на етапі виконання на основі типу об’єкта, який передається їй в якості аргументу –покажчика this.

Всередині функції main() покажчик базового класу р по черзі  посилається на об’єкти  типа base, p1 і p2!. Першим покажчику р привласнюється адреса об’єкта  obj (об’єкта  типу base). При виклику функції func () через покажчик р використовується її версія з класу base. Далі покажчику р привласнюється адреса об’єкта obj1 і функція func() викликається знову. Оскільки версія викликаючої  віртуальної функції визначається типом об’єкту, на який посилається покажчик, то викликається та версія функції, яка пере визначається в класі p1. Вкінці , покажчику р привласнюється адреса об’єкта obj2 і знову викликається функція func(). При цьому викликається та версія функції func(),яка пере визначається в класі p2.

2)Віртуальні функції мають ієрархічний порядок наслідування. Якщо віртуальна функція не підміняється в похідному класі, то використовується версія функції, визначеної в базовому класі.

#include <iostream>

using namespace std;

class base

{

public:

 int i;

base (int x) {i = x;}

 virtual void func()

{

 cout <<"Выполнение функции func( ) базового класса: ";

 cout <<i <<'\n';

}

};

class p1: public base

{

public:

p1(int x) : base(x) { }

 void func ( )

{

 cout<< "Выполнение функции func() класса derivedl: ";

 cout<< i * i << '\n';

}

};

class p2: public base

{

public :

p2(int x) : base(x) { }

// в классе p2 функция func ( ) не подменяется

};

int main()

{

base *p;

base obj(10);

p1 objl(10);

p2 obj2(10);

p = &obj;

p->func(); // функция func ( ) базового класса

р = &obj1;

p->func(); // функция func{) производного класса derivedl

р = &obj2;

p->func(); // функция func() базового класса

return 0;

}

Результат виконання програми:

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

25.2 Чисто віртуальна функція

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

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

virtual тип ім’я_функції(списох_параметрів) = 0;

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

(Це повідомляє компілятору, що в базовому класі не існує тіла функції. Якщо функція задається як чисто віртуальна, то вона обов’язково повинна підмінятися в кожному похідному класі. Інакше при компіляції виникне помилка. Таким чином, створення чистих віртуальних функцій — гарантує, що похідні класи забезпечать їх пере визначенням.

25.3 Абстрактний класс

Якщо клас містить хоча б одну чисто віртуальну функцію(своя чи наслідувана), то він є абстрактним.

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

25.4 Поліморфізм

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

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

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

За допомогою  віртуальних функцій об’єкт  сам визначає, яку функцію йому необхідно викликати, під час виконання програми. Техніку використання віртуальних функцій називають поліморфізмом. Буквально поліморфізм означає – володіти багатьма формами. Об’єкт в вашій програмі, насправді, може представляти не один клас, а ієрархію класів і поведінка об’єктів цих класів в ієрархії буде різною.

Згідно правилам С++, покажчик на базовий клас може посилатися на об’єкт цього і будь-якого похідного класу. Наприклад, є ієрархія класів: А – базовий , В – похідний  від А, С – похідний від В (рис. 25.1).

Рисунок 25.1 – Взаємозвязок міз базовими і похідними класами

В програмі об’єкти  цих класів можуть оголошуватися, наприклад, таким чином.

Код:

A object_A; //оголошення  об’єкта  типу А
B object_B; // оголошення  об’єкта  типу В
C object_C; // оголошення  об’єкта  типу С

Згідно даному правилу покажчик типу А може вказувати на будь-який з цих трьох об’єктів:

Код:

A *point_to_Object; // оголошуємо покажчик на базовий клас
point_to_Object=&object_C; //привласнимо покажчику адресу об’єкта С
point_to_Object=&object_B; // привласнимо покажчику адресу об’єкта В

А наступний код не правильний:

Код:

В *point_to_Object; // оголошуємо покажчик на похідний клас
point_to_Object=&object_А; //неможна привласнювати покажчику адресу базового об’єкта

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

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


Тема 26 Статичні функції

Змінні — елементи класу можна оголошувати як статичні (static). Використовуючи статичні компонентні змінні, можна вирішити декілька непростих проблем. Якщо ви оголошуєте змінну статичною, то може існувати тільки одна копія цієї змінної — незалежно від того, скільки об’єктів даного класу створюється. Кожний об’єкт просто використовує (сумісно з іншими) цю одну змінну. (Для звичайних компонентних змінних при створені кожного об’єкта створюється їх нова копія, і доступ до кожної копії можливий тільки через цей об’єкт. Таким чином, для звичайних змінних кожний об’єкт володіє власними копіями змінних.) З іншої сторони, є тільки одна копія статичної змінної — елемента класу, і всі об’єкти  класу використовують її сумісно. Крім того, одна і та ж статична змінна буде використовуватися всіма класами, похідними від класу, в якому ця статична змінна міститься.

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

При оголошені, статичні дані в середині класу не визначаються. Визначити їх треба поза класом. Для того щоб вказати до якого класу статична змінна належить, необхідно переобявити (redeclare) її, використовуючи операцію розширення області видимості(::).

Всі статичні змінні за замовчуванням ініціалізуються нулем. Але, за необхідністю, статичній змінній класу можна дати довільне початкове значення.

Головне призначення статичних змінних-елементів – вони заміняють використання глобальних змінних. Якщо при роботі з класами задавати глобальні змінні, то це практично завжди порушує принцип інкапсуляції, що є фундаментальним принципом OOP і C++.

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

Статичні компонентні функції:

  •  немають покажчика this
  •  не можуть бути віртуальними
  •  не можуть оголошуванися з ідентифікаторами const (постійний) і volatile (змінний)
  •  можуть викликатися будь-яким об’єктом цього класу, а також через ім’я класу і оператор розширення області видимості без всякого зв’язку з об’єктом цього класу.

1. Простий приклад використання статичної змінної-елемента.

// Приклад статичної змінної-елемента

#include <iostream>

using namespace std; //нем

class myclass

{

static int i; //доступ закритий

public:

 void seti(int n) { i = n; }

 int geti() { return i; }

};

// Визначення «myclass::i». Змінна i залишається

// закритим елементом класу myclass

int myclass::i;

int main()

{

myclass o1, o2;

o1.seti(10);

cout <<"ol.i: "<< o1.geti() <<'\n'; // виводить значення 10

cout <<"o2.i: "<< o2.geti() <<'\n'; // також виводить 10

return 0;}

ol.i:10

o2.i:10

Фактично тільки для об’єкта o1 встановлюється значення статичної компонентної змінної i. Але оскільки змінна i сумісно використовується об’єктами ol і о2 (і, насправді, всіма об’єктами типу myclass), обидва виклики функції geti() дають один і той же результат.

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

2. Осцільки статична змінна — елемент класу існує ще до створення об’єкту цього класу, доступ до неї в програмі може бути реалізований без будь-якого обєкту. Наприклад, в наступному варіанті попередньої програми для статичної змінної i встановлюється значення 100 без прив’язки до конкретного об’єкта. Зверніть увагу на використання оператору розширення області видимості для доступу до змінної i.

// Для звернення до статичної змінної об’єкт не потрібен

#include <iostream>

using namespace std;

class myclass

{

public:

static int i;

 void seti(int n) { i = n; }

 int geti() { return i; }

};

int myclass::i;

int main ()

{

myclass o1, o2;

// значення безпосередньо задається змінній i

myclass::i = 100; // об’єкти не використовуються

cout <<"ol.i: "<< o1.geti() <<'\n'; // виводить 100

cout <<"o2.i: "<< o2.geti() <<'\n'; // також виводить 100

return 0;}

Так як статичній змінній i присвоєно значення 100, програма виводить на екран наступне:

ol.i: 100

o2.i: 100

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

#include <iostream>

using namespace std;

class static_func_demo

{

static int i;

public:

 static void init(int x) { i = x; }

 void show() { cout << i; }

};

int static_func_demo::i; // визначення змінної i

int main()

{

// ініціалізація статичних даних ще до створення об’єкта

static_func_demo::init(100);

static_func_demo x;

x.show(); // виведення на екран значення 100

return 0;}

100

Тут виклик функції init() ініціалізує змінну i ще до створення об’єкту  типу static func demo.

Тема 27 Обробка виключних ситуацій

Тема 28 Оператори try, catch, throw

28.1 Виключення

Виключення – це помилки, які виникають під час роботи програми. Причинами виникнення помилок може бути:

  •  вихід за межі пам’яті;
  •  помилка відкриття файлу;
  •  спроба ініціалізувати об’єкт недопустимими значеннями ;
  •  використання індексу, який виходить за межі масиву і ін..

C++ забезпечує вбудований механізм обробки помилок, які називаються обробкою виключних ситуацій (exception handling). Завдяки обробці виключних ситуацій можна спростити керування і реакцію на помилки під час виконання програм. Обробка виключних ситуацій в C++ реалізується за допомогою трьох ключових слів; try(блок повторних спроб), catch(обробник помилок чи блок пастка) і throw(генерація помилок).

1. В наступному дуже простому прикладі показано, як в C++ функціонує система обробка виключних ситуацій:

// Простий приклад  обробки виключної ситуації

#include <iostream>

using namespace std;

int main()

{

cout<<"begin\n";

 try { // початок блоку try

 cout<<"V bloci try\n";

 throw 10; // збудження помилки

 cout<<"Cya instrukciya ne bude vykonana";

 }

 catch (int i) { // перехоплення помилки

 cout<<"perehoplene pomylka #";

 cout<<i<<"\n";

}

cout<<"end\n";

 return 0;

}

Результа виконання програми:

Блок try повинен містити ту частину вашої програми, в якій ви хочете від слідкувати  помилки. Це може бути як декілька інструкцій всередині однієї функції, так і всі інструкції всередині функції main( ) (що очевидно веде до від слідкування помилок у всій програмі). (В блоці try розміщують інструкції програми, під час виконання яких треба забезпечити обробку виключних ситуацій.) Якщо виключна ситуація (тобто помилка) присутня всередині блоку try, вона викликається інструкцією throw і перехоплюється відповідною інструкцією catch, яка її обробляє. (Після того як виключна ситуація виникла, вона перехоплюється відповідною інструкцією catch, яка її обробляє.)

Будь-яка інструкція, яка викликає виключну ситуацію, повинна виконуватися всередині блоку try.

Будь-яка виключна ситуація повинна перехоплюватися інструкцією catch, яка розміщується безпосередньо за блоком try,  який викликає виключну ситуацію.

Далі представлена основна форма інструкцій try і catch:

try {

// блок збудження(виклику) виключної ситуації

}

catch (typel arg-) {

// блок перехоплення виключної ситуації

}

catch (type-2 arg) {

// блок перехоплення виключної ситуації

}

catch (type3 arg) {

// блок перехоплення виключної ситуації

}

.

catch (typeN arg) {

// блок перехоплення виключної ситуації

}

З блоком try може пов’язуватися  декілька інструкцій catch. Tе, яка саме інструкція catch використається, залежить від  типу виключної ситуації. Тобто, якщо тип даних, вказаний в інструкції catch, відповідає типу виключної ситуації, виконується дана інструкція catch. При цьому решта інструкцій блоку try ігноруються (тобто зразу після того, як якась інструкція в блоці try спричинила появу виключної ситуації, управління передається відповідній інструкції catch, оминувши решту інструкцій блоку try, — примеч. пер.). Якщо виключна ситуація перехоплена, аргумент arg отримує її значення. Якщо вам не потрібний доступ до самої виключної ситуації, можна в інструкції catch вказати тільки її тип type, аргумент arg вказувати  не обов’язково. Можна перехоплювати будь-які типи даних, включаючи і типи створених вами класів. Фактично в якості виключних ситуацій часто використовуються саме типи класів.

Далі представлена основна форма інструкції throw:

throw виключна _ ситуація ;

Інструкція throw повинна виконуватися або всередині блока try, або в будь-якій функції, яку цей блок викликає (прямо чи ні). Тут виключна _ ситуація  — це збуджена інструкцією throw. Якщо ви збуджуєте виключну ситуацію, для якої нема відповідної інструкції catch, може виникнути ненормальне завершення програми.( Якщо ваш компілятор функціонує в відповідності зі стандартом Standard C++, то збудження незворотної виключної ситуації приводить до виклику стандартної бібліотечної функції terminate(). За замовчуванням для завершення програми функція terminate() викликає функцію abort(), але при бажанні можна задати власну процедуру завершення програми. )

2. Тип виключної ситуації повинен відповідати типу ,заданому в інструкції catch. Наприклад, в попередньому прикладі, якщо змінити тип даних в інструкції catch на double, то виключна ситуація не буде перехоплена і буде мати місце  ненормальне завершення програми. Це продемонстровано в наступному фрагменті:

//Цей приклад не буде працювати

#include <iostream>

using namespace std;

int main ()

{

cout<<"begin\n";

 try { // початок блоку try

 cout<<"V bloci try\n";

 throw 10; //  збудження помилки

 cout<<"Cya instrukciya ne bude vykonana";

 }

 catch (double i) { // Ця інструкція не будет працювати

 // з виключною ситуацією цілого типу

 cout<<"perehoplene pomylka #";

 cout<<i<<"\n";

}

cout<<"end";

 return 0;

}

Оскільки виключна ситуація цілого типу не буде перехоплена інструкцією catch типу double, на екран програма виведе наступне:

Рисунок 26.1 – Вікно генерації помилки

3. Виключна ситуація може бути збуджена не входящою в блок try інструкцією, якщо сама ця інструкція входить в функцію, яка викликається з блоку try. Наприклад:

/* Збудження з функції, яка знаходиться поза блоком try */

#include <iostream>

using namespace std;

void Xtest(int test)

{

cout<<"Vseredyni v funkcii Xtest, test= " 

 <<test<<"\n";

 if (test) throw test;

}

int main()

{

cout<<"begin\n";

 try { // начало блока try

 cout<<"Vseredyni bloka try\n";

 Xtest (0);

 Xtest (1);

 Xtest (2);

}

 catch (int i){ // перехват ошибки

 cout<<"perehoplena pomylka nomer: ";

 cout<<i<<"\n";

}

cout<<"end\n";

 return 0;

}

4. Блок try можно розміщувати в функції. В цьому випадку при кожному вході в функцію обробник виключної ситуації  встановлюється знову.

#include <iostream>

using namespace std;

// Блоки try и catch могут находиться не только в функции main ( )

void Xhandler (int test)

{

 try {

 if (test) throw test;

}

 catch(int i) {

 cout<<" perehoplena pomylka nomer: "

  <<i<<'\n';

}

}

int main ()

{

cout<<" begin\n";

Xhandler (1) ;

Xhandler (2) ;

Xhandler (0) ;

Xhandler (3) ;

cout<<" end\n";

return 0;

}

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

Можна перехоплювати будь-які типи даних, включаючи і типи створених вами класів. Фактично в якості виключних ситуацій часто використовуються саме типи класів.

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

const int MAX = 3;

class masyv

{

private:

 int mas[MAX];

 int i;

public:

 class Vykluch{//тіло класу пусте};

masyv()

{

 i=-1;

}

 void push(int j)

{

 if(i>=MAX-1)

  throw Vykluch();

 mas[++i]=j;

}

 int pop()

{

 if(i<0)

  throw Vykluch();

 return mas[i--];

}

};

int main()

{

masyv m1;

 try

{

 m1.push(11);

 m1.push(22);

 m1.push(33);

 //m1.push(44);//Виключення! Масив заповнений

 cout<<"1: "<<m1.pop()<<endl;

 cout<<"2: "<<m1.pop()<<endl;

 cout<<"3: "<<m1.pop()<<endl;

 cout<<"4: "<<m1.pop()<<endl;//Виключення ! Масив пустий

}

 catch(masyv::Vykluch)

{

 cout<<"Vykluch:Masyv perepovnenyj abo pustyj"

  <<endl;

}

cout<<"Normalnj vyhid"<<endl;

 return 0;

}

Спочатку створюємо клас masyv в якому можуть виникнути помилки. Клас виключень визначений в відкритій частині класу масив. Методи класу масив підлягають старанній перевірці на предмет виявлення помилок. Якщо такі є, то за допомого ключовог ослова throw і конструктора класу виключень виконуємо генерацію викл. ситуації

Опис класу виключення

Він описується всередині класу masyv class Vykluch{//тіло класу пусте};

Обєкт цього класу немає ні даних ні методів. Роль грає тільки імя класу. Воно потрібне для повязування виразу генерації помилки з обробником виключних ситуацій(клас не обвязково повинен бути пустим).

Генерація виключень( throw)

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

Vykluch();

друге слово неявно запускає конструктор класу Vykluch для створення відповідно обєкту цього класу. Перше слово передає керування програмою обробнику помилок.

Блок повторних спроб(try)

Всі вирази в main , в яких можуть виникнути помилки, тобто вирази маніпуляції з даними обєкту класа masyv, полягають в фігурні дужки, перед якими стаїть кл. слово try:

try

{  

// код робота з обєктами в яких можуть виникнути помилки

}

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

Обробник помилок (блок пастка)

Код, в якому містяться операції по обробці помилок, полягають в фігурні дужки і починаються з кл.слова  catch. Імя класу виключень повинно включати в себе найменування класу в якому він живе. В даному прикладі це

catch(masyv::Vykluch)

{

 //код обробника помилок

}

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

Послідовність дій програми при виникненні помилки:

  1.  код нормально виконується поза блоком повторних спроб;
  2.  керування переходить в блок повторних спроб;
  3.  якісь інструкції в цьому блоці спричиняють помилки(в методі);
  4.  (метод) генерує помилку.

Керування переходить до обробника помилок(), який слідує зразу за блоком повторних проб


Список літератури 

Дейтел Х.М., Дейтел П.Дж. Как программировать на Си++. – М.: ЗАО БИНОМ, 1999. – 1000 с.

Шилдт Г.Самоучитель С++: Пер с англ.. – 3-е изд. – СПб.: БВХ–Петербург, 2003. – 688 с.

Шилдт Г.С++:руководство для начинающих.2-е узд.:Пер. с англ. – М.: «Вильямс», 2005. – 672 с.

Прата С. Язык программирования С++.Лекции и упражнения.Учебник:Пер. с англ. – СПб.: ООО «ДиаСофтЮП», 2005. – 1004 с.

Брайан О. С++ без страха: учебное пособие:пер. с англ.. –М.: Изд-во Триумф, 2005. – 432 с.

Довбуш.Г.Ф. Visual C++ на примерах /Г.Ф. Довбуш, А.Д.Хомоненко/ Подред. Проф. А.Д.Хомоненко. – СПб.: БХВ-Петербург, 2007. – 528 с.

Лафоре Р. Объектно – ориентированое программирование в С++.4 –е издание. – СПб.:Питер, 2004. – 922 с.

Пол И..ООП с использованием С++. – Киев:  "ДиаСофт", 1995.

Рассохин Д. От С к С++. – Москва: "ЭДЭЛЬ", 1993.

. Голуб А.И. Правила программирования на С & С++. – Москва: "БИНОМ", 1996.

Сэвитч У. С++ в примерах. – М.: ЭКОМ, 1997. – 736 с.

Вайнер Р., Пинсон Л. С++ изнутри. – Киев: НИПО "ДиаСофт", 1993. – 304 с.

Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на С++. – М.: "Издательство Бином", СПб: "Невский диалект", 1998 г. – 560 с.


заборонено

дозволено

дозволено

дозволено

окажчик на базовий клас А

Покажчик на клас В

Базовий клас А

Методи і дані базового класу

Похідний клас В

Наслідувані з А методи і дані

Нові методи і дані

Похідний клас С

Наслідувані з А і з В методи і дані

Нові методи і дані

дозволено

дозволено

індекс стовпця

індекс рядка

імя масиву


 

А также другие работы, которые могут Вас заинтересовать

26274. Урожайность яровой пшеницы (т/га) на выщелоченных черноземах в производственных опытах СибНИИЗХим, Новосибирская область 263.5 KB
  Порядок формирования технологий возделывания сельскохозяйственных культур, их региональные и федеральные регистры. Наборы технологий разрабатывают применительно к различным агроэкологическим группам земель, для разных уровней интенсификации производства и категорий товаропроизводителей на основе нормативов.
26275. Архивное законодательство в 2000-е гг 56 KB
  Последнему непосредственно подчинены 15 федеральных государственных архивов Архивы в системе архивной службы РФ Федеральному архивному агентству непосредственно подчиняются 15 федеральных государственных архивов Всероссийский научноисследовательский институт документоведения и архивного дела ВНИИДАД и 1 обслуживающая организация.2004 Положение о ФАА – положение регламентирует отношения сроки сферу использования сеть архивов обязанности сторон отраслевые фонды –имеющие право постоянного хранения документов. принимает решение о выдаче...
26276. Организация комплектования Архивного Фонда Российской Федерации и других архивных документов 21.85 KB
  Целью комплектования является наиболее полная концентрация в архиве профильных ему документов. Мероприятия входящие в понятие комплектования: Определение состава источников; ЭЦД и НТО; Прием документов в государственные муниципальные архивы. Понятие источник комплектования появилось в 1940ые годы это учреждения или лица непосредственно передающие документы в государственные или ведомственные архивы.
26277. Теоретические основы и организация проведения экспертизы ценности документов Архивного Фонда Российской Федерации и других архивных документов 33.06 KB
  Отбор документов на гос.выделение документов к уничтожению 5. Систему составляют три группы критериев: происхождение документов их содержание и внешние особенности.
26279. Учет архивных документов 70 KB
  10 Роль учета: Одним из важнейших направлений деятельности государственных и муниципальных архивов является осуществление государственного учёта архивных документов. Учёт одновременно является средством обеспечения сохранности документов их адресного поиска а также создаёт возможность для разработки и использования информационных справочников. На основе данных учётных документов рассчитываются штатная численность сотрудников архивов площади и оборудование необходимые для хранения документов и осуществляется финансирование.
26280. Обеспечение сохранности документов 48 KB
  Обеспечение сохранности документов до середины 30х гг. понималось в комплексе с другими направлениями сбор концентрация в архивах использование документов когда комплектование и использование документов оформляются как самостоятельное направление – проблема обеспечения сохранности включала учет и организацию хранения документов. она связывалась с эвакуацией и реставрацией документов.
26281. Система НСА к архивным документам 19.24 KB
  Назначение СНСА. Понятие первичная информация вторичная информация СНСА архива СНСА АФ РФ Архивный справочник отражение вопросов связанных с НСА в законодательных и нормативнометодических актах РФ в 1990е 2000е годы. Требования предъявляемые к СНСА к документам государственных муниципальных архивов. СНСА это комплекс взаимосвязанных и взаимодополняемых архивных справочников о составе и содержании архивных документов создаваемых на единой методической основе для поиска архивных документов и архивной информации в целях...
26282. Организация документов АФ РФ и других архивных документов в архиве 62 KB
  Организация документов АФ РФ и других архивных документов в архиве Архивы в зависимости от их функций и подчиненности можно разделить на две группы: государственные и ведомственные. Государственные архивы учреждения которые осуществляют собирание хранение и организацию архивных документов в целях их всестороннего использования. Наиболее ценная часть документов архива по истечении срока хранения в ведомстве передается на постоянное хранение в государственный архив. Архивный фонд Российской Федерации это исторически сложившаяся и...