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

lr09

.pdf
Скачиваний:
4
Добавлен:
19.02.2016
Размер:
913.13 Кб
Скачать

Лабораторна робота № 9

Класи

Мета роботи

Метою лабораторної роботи є ознайомлення з реалізацією основ об’єктно-орієнтованого програмування мовою C++.

Короткі теоретичні відомості до роботи

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

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

властивості (представлені як елементи даних класу) і варіанти поведінки або операції

(представлені як функції-елементи класу).

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

Клас можна представляти як деякий шаблон, який визначає формат об’єкта. В C++ специфікація класу використовується для побудови об’єктів. Об’єкти — це екземпляри класу. Оголошення класу задає представлення об'єктів цього класу і набір операцій, які можна застосовувати до таких об'єктів. По суті, клас являє собою набір планів, які визначають, як будувати об’єкт.

В класі дані оголошуються в виді змінних, а код оформляється в виді функцій. Функції і змінні, які складають клас, називаються його членами. Таким чином, змінна, оголошена в класі, називається членом даних, а функція, оголошена в класі, називається функцією-членом.

Клас оголошується за допомогою ключового слова class. Загальний формат оголошення

класу має вид:

class ім’я_класу { [private:]

закриті дані і методи public:

відкриті дані і методи } список_об’єктів;

де

class — ключове слово,

ім’я_класу — ім’я класу — це ім’я нового типу, яке можна використовувати для створення об’єктів класу.

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

1

присутній список об’єктів або ні, необхідно ставити ; (крапку з комою) після закриваючої дужки опису класу.

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

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

Визначення класів і створення об’єктів

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

class Monstr

{

// закриті елементи класу int health, ammo;

public:

// відкриті елементи класу

void draw(int x, int y, int scale, int position); int get_health();

int get_ammo();

void set_ammo(int amount);

};

Уцьому класі два приховані члени даних (або поля) — health і ammo, безпосередньо отримати або змінити значення яких ззовні неможливо. Отримати їх значення можна за допомогою відкритих функцій-членів (або методів) get_health() і get_ammo(). Для зміни одного з полів передбачена відкрита функція-член set_ammo(). Доступ до полів за допомогою методів в даному випадку може здатися штучним ускладненням, але потрібно враховувати, що полями реальних класів можуть бути складні динамічні структури, і отримати значення їх елементів не так тривіально.

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

Визначення функцій-членів класу

Хоча функції draw(), get_health() та інші оголошені в класі Monstr, вони ще не визначені. Для визначення функції-члена треба зв'язати ім'я класу, частиною якого є функціячлен, з ім'ям функції. Це досягається шляхом написання імені функції услід за ім'ям класу з двома двокрапками (::).

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

2

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

{

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

}

Нижче наведені приклади визначення функцій-членів get_ammo() і set_ammo():

int Monstr::get_ammo()

{

return ammo;

}

void Monstr::set_ammo(int amount)

{

ammo = amount;

}

Тіло невеликої функції-члену може бути визначене усередині класу, тоді така функція-член є вбудованою (inline). Наприклад, визначення класу Monstr з визначенням вбудованої функції get_health():

class Monstr

{

// закриті елементи класу int health, ammo;

public:

// відкриті елементи класу

void draw(int x, int y, int scale, int position); int get_health() {return health;}

int get_ammo();

void set_ammo(int amount);

};

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

inline int Monstr::get_ammo() { return ammo;}

Створення об’єктів

Оголошення класу Monstr не призводить до створення об’єктів типу Monstr. Щоб створити об'єкт відповідного класу, треба просто використати ім'я класу як специфікатор типу даних. Наприклад:

Monstr frog, snake;

Звернення до членів класу

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

Наприклад:

frog.set_ammo(10000); frog.draw(30, 20, 100, 0);

Доступ до елементів даних класу можна отримати і з допомогою покажчика на об'єкт цього класу. Наступний приклад це ілюструє.

// Демонстрація способів доступу до членів класу

#include "stdafx.h"

class Coord

{

public:

int x, y;

void SetCoord(int _x, int _y);

};

3

void Coord::SetCoord(int _x, int _y)

{

x= _x;

y= _y;

}

int _tmain(int argc, _TCHAR* argv[])

{

Coord pt1, pt2;

pt1.x = 0; //об'єкт.член_класу pt1.y = 0;

pt1.SetCoord(10, 20);

Coord *ptPtr = &pt2; //покажчик на об'єкт ptPtr->x = 10; // покажчик->член_класу ptPtr->y = 10;

ptPtr->SetCoord(100, 200);

return 0;

}

Специфікатори доступу до членів класу

Управління доступом застосовується однаково до функцій-членів класу та даних-членів класу.

Член класу може бути:

приватним (private) — це означає, що ім'я члену може вживатися лише всередині функцій-членів класу та друзів класу, в якому цей член класу оголошений;

захищеним (protected) — це означає, що ім'я члену може вживатися лише всередині функцій-членів класу, друзів цього класу і похідних від нього класів;

публічним (public) — це означає, що ім'я члену може вживатися як всередині класу так і за межами класу.

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

Модифікатори доступу можна використовувати кілька разів в одному і тому ж оголошенні класу.

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

ане до проблем мови.

ВC++ структури та об’єднання розглядаються як специфічні типи класів. Одна з ключових відмінностей між класами і структурами полягає в наступному. Члени класу без спеціфікатора доступу за замовчанням є приватними. Члени структур та об'єднань за замовчуванням є

публічними.

4

Конструктори

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

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

Конструктор має те ж ім'я, що і клас, членом якого він являється, і не повертає ніякого значення.

Наприклад, клас представлення комплексного числа Complex з однойменним конструктором Complex():

// Демонстрація конструктора класу

#include "stdafx.h" #include <iostream>

using namespace std;

class Complex

{

private:

double r, m;

public:

Complex(); // Конструктор (оголошення в каласі) void Show();

};

// Конструктор (визначення)

Complex::Complex()

{

cout << "Виклик конструктора\n"; r = 0;

m = 0;

}

void Complex::Show()

{

cout << r << " + i" << m << endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

Complex cx; cx.Show();

return 0;

}

В даному прикладі конструктор класу Complex виводить повідомлення на екран та ініціалізує значення закритих полів даних: r, m.

Конструктор з параметрами

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

5

передавати

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

попередній

приклад класу комплексного числа конструктором з параметрами

Complex(double real, double imag).

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

#include "stdafx.h" #include <iostream>

using namespace std;

class Complex

{

private:

double r, m;

public: Complex();

Complex(double real, double imag); // Конструктор з параметрами

// (оголошення)

void Show();

};

Complex::Complex()

{

cout << "Виклик конструктора\n"; r = 0;

m = 0;

}

// Конструктор з параметрами (визначення) Complex::Complex(double real, double imag)

{

cout << "Виклик конструктора з параметрами\n"; r = real;

m = imag;

}

void Complex::Show()

{

cout << r << " + i" << m << endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

Complex cx; cx.Show();

Complex cx1(-12, 18); // Виклик конструктора з параметрами cx1.Show();

return 0;

}

Виклик конструктора

0 + i0

Виклик конструктора з параметрами

-12 + i18

Фактично синтаксис передачі аргументів конструктору з параметрами при оголошенні змінної cx1 є скороченою формою запису наступного виразу:

Complex cx1 = Complex(-12, 18);

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

6

Конструктор за замовчуванням

Конструктор за замовчуванням деякого класу Class_Х — це конструктор класу Class_Х, який не має параметрів і викликається при створенні об’єкта без передачі аргументів. Конструктор за замовчуванням зазвичай має вигляд

Complex::Complex();

Конструктор, який має параметри за замовчуванням, наприклад

Complex::Complex(double real = 0, double imag = 0);

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

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

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

Конструктор копіювання для класу Class_Х — це конструктор, який викликається для створення об'єкту класу Class_Х, копіюючи при цьому дані з вже існуючого об’єкту даного класу. Такий конструктор може бути викликаний з одним параметром — посиланням на об'єкт класу Class_Х, який буде джерелом даних для ініціалізації нового об’єкту. Конструктор копіювання може бути оголошений, наприклад, так:

Complex::Complex(const Complex& src);

// Демонстрація конструктора копіювання

#include "stdafx.h" #include <iostream>

using namespace std;

class Complex

{

private:

double r, m;

public:

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

Complex(double real = 0, double imag = 0);

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

Complex(const Complex &src); void Show();

};

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

Complex::Complex(double real, double imag)

{

cout << "Виклик конструктора за замовчуванням/з параметрами\n"; r = real;

m = imag;

}

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

Complex::Complex(const Complex &src)

{

cout << "Виклик конструктора копіювання\n";

7

r = src.r; m = src.m;

}

void Complex::Show()

{

cout << r << " + i" << m << endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

SetConsoleOutputCP(1251);

Complex cx(5.0, 6.3); // Виклик конструктора з параметрами cx.Show();

Complex cx1 = cx; // Виклик конструктора копіювання cx1.Show();

Complex cx2(cx); // Виклик конструктора копіювання cx2.Show();

return 0;

}

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

5 + i6.3

Виклик конструктора копіювання

5 + i6.3

Виклик конструктора копіювання

5 + i6.3

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

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

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

Будь-який конструктор копіювання (визначений користувачем або згенерований компілятором) – використовується у випадку:

ініціалізації об’єктів-змінних;

8

передачі об’єктів-аргументів до функції;

поверненні об’єкта з функції;

обробці виключних ситуацій.

Деструктор

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

Деструктор має те ж ім’я, що і клас, до якого він належить, але з символом ~ спереду, не має параметрів і не повертає значення. Наприклад, для класу Class_Х деструктор матиме ім'я

~Class_Х.

Деструктор автоматично викликається, у випадку коли:

автоматичний або тимчасовий об'єкт виходить з області дії;

завершується програма (для статично сконструйованих об'єктів);

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

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

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

// Демонстрація конструктора копіювання

//

#include "stdafx.h" #include <iostream>

using namespace std;

class Complex

{

private:

double r, m;

public:

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

Complex(double real = 0, double imag = 0);

~Complex(); //Деструктор void Show();

};

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

Complex::Complex(double real, double imag)

{

cout << "Виклик конструктора за замовчуванням/з параметрами\n"; r = real;

m = imag;

}

//Деструктор

Complex::~Complex()

{

cout << "Виклик деструктора\n";

}

9

void Complex::Show()

{

cout << r << " + i" << m << endl;

}

int _tmain(int argc, _TCHAR* argv[])

{

Complex cx(5.0, 6.3); // Виклик конструктора з параметрами cx.Show();

return 0;

}// Виклик (неявний) деструктора cx в кінці програми

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

5 + i6.3

Виклик деструктора

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

вкласі не виділяється динамічна пам’ять і, відповідно, немає необхідності її звільняти.

Масиви об’єктів класу

З об'єктів класу, як і із звичайних змінних, можна будувати масиви. Синтаксис оголошення масиву об'єктів аналогічний синтаксису оголошення масивів звичайних змінних. Наприклад, наступне оголошення створює масив з 10 елементів, які є об'єктами класу AnyClass:

AnyClass obArr[10];

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

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

// Демонстрація створення масиву об’єктів

#include "stdafx.h" #include <iostream>

using namespace std;

class AnyClass

{

int a; public:

AnyClass(int n) {a = n;} int GetA() {return a;}

};

int _tmain(int argc, _TCHAR* argv[])

{

//Оголошення і ініціалізація масиву об'єктів

AnyClass obArr[5] = {13, 11, 21, 23, 21}; int i;

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

{

// Звернення до елементів масиву cout << obArr[i].GetA() << ' ';

}

cout << '\n'; return 0;

}

10

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