Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
+ООП_Навч_посібник.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
6.58 Mб
Скачать

24.6. Кроки написання коду програми

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

Варіанти використання, визначені на етапі удосконалення, стають ітераціями на новому етапі (див. рис. 24.2). У великому проекті кожна ітерація може проводитися різними групами програмістів. Всі ітерації повинні проектуватися окремо, а їх результати – надаватися замовнику для внесення доповнень і виправлень. У нашій невеликій| програмі, втім, ці зайві складнощі ні до чого.

24.6.1. Написання заголовного файлу

Ура, ми дісталися до написання таких рідних і звичних файлів з кодами програми. Краще за все починати із заголовного (*.h) файлу, у якому необхідно визначити тільки інтерфейсні частини класів, але не подробиці реалізації. Як зазначалося раніше, оголошення в заголовному файлі – це загальнодоступна частина класів. Тіла функцій, що є розташовані в *.срр-файлах, називаються реалізацією і користувачам недоступні.

Написання заголовного файлу – це проміжний крок між етапами плануванням і звичайним кодуванням методів. Розглянемо вміст заголовного файлу landlord.h.

Код програми 24.1. Заголовний файл до програми landlord

// landlord.h

// Заголовний файл landlord.cpp – містить оголошення класів і т.ін.

#pragma warning(disable:4786)//Для множин (тільки компілятори Microsoft)

#include <iostream> // Для потокового введення-виведення

#include <vector> // Для роботи контейнерним класом "Вектор"

#include <set>

#include <string> // Для роботи з рядками

#include <algorithm> // Для sort()

#include <numeric> // Для accumulate()

using namespace std; // Використання стандартного простору імен

/// Глобальні методи ///

void getaLine(string& inStr); // Отримання рядка тексту

char getaChar(); // Отримання символу

class tenant { // Мешканці

private:

string name; // Ім'я мешканця

int aptNumber; // Номер кімнати мешканця

// Тут може бути будь-яка інша інформація про мешканця,

// наприклад, номер телефону і т.ін.

public:

tenant(string n, int aNo);

~tenant();

int getAptNumber(); // Потрібно для використання в множині

friend bool operator < (const tenant&, const tenant&);

friend bool operator == (const tenant&, const tenant&);

// Для операцій введення/виведення

friend ostream& operator << (ostream&, const tenant&);

}; // <------> Кінець оголошення класу tenant

// Функціональний об'єкт для порівняння імен мешканців

class compareTenants {

public:

bool operator () (tenant*, tenant*) const;

};

// >------> Клас tenantList

class tenantList {

private:

// Встановити покажчики на мешканців

set<tenant*, compareTenants> setPtrsTens;

set<tenant*, compareTenants>::iterator iter;

public:

~tenantList(); // Оголошення деструктора (Видалення мешканців)

void insertTenant(tenant*); // Внесення мешканця в перелік

int getAptNo(string); // Повертає номер кімнати

void display(); // Виведення переліку мешканців

};

// <------> Кінець оголошення класу tenantList

class tenantInputScreen {

private:

tenantList* ptrTenantList;

string tName;

int aptNo;

public:

tenantInputScreen(tenantList* ptrTL): ptrTenantList(ptrTL)

{ /* тут порожньо */ }

void getTenant();

}; // <------> Кінець класу tenantInputScreen

// Один рядок таблиці прибутку: адреса і 12 місячних плат

class rentRow {

private:

int aptNo;

float rent[12];

public:

rentRow(int); // Оголошення конструктора з одним параметром

void setRent(int, float); // Запис плати за місяць

float getSumOfRow(); // Сума платежів з одного рядка

// Потрібно для збереження в множині

friend bool operator < (const rentRow&, const rentRow&);

friend bool operator == (const rentRow&, const rentRow&);

// Для виведення

friend ostream& operator << (ostream&, const rentRow&);

}; // <------> Кінець класу rentRow

// Функціональний об'єкт порівняння об'єктів rentRows

class compareRows {

public:

bool operator () (rentRow*, rentRow*) const;

};

class rentRecord {

private:

// Множину покажчиків на об'єкти rentRow (по одному на мешканця)

set<rentRow*, compareRows> setPtrsRR;

set<rentRow*, compareRows>::iterator iter;

public:

~rentRecord();

void insertRent(int, int, float);

void display();

float getSumOfRents(); // Сума усіх платежів

}; // <------> Кінець класу rentRecord

class rentInputScreen {

private:

tenantList* ptrTenantList;

rentRecord* ptrRentRecord;

string renterName;

float rentPaid;

int month;

int aptNo;

public:

rentInputScreen(tenantList* ptrTL rentRecord* ptrRR):

ptrTenantList(ptrTL), ptrRentRecord(ptrRR)

{ /*тут пусто*/ }

void getRent(); // Орендна плата одного мешканця за один місяць

}; // <------> Кінець класу rentInputScreen

class expense {

public:

int month, day;

string category, payee;

float amount;

expense() { }

expense(int m, int d, string c, string p, float а):

month(m), day(d), category(c), payee(p), amount(a)

{ /* тут порожньо! */ }

// Потрібно для зберігання в множині

friend bool operator < (const expense&, const expense&);

friend bool operator == (const expense&, const expense&);

// Потрібно для виведення

friend ostream& operator << (ostream&, const expense&);

}; // <------> Кінець класу expense

// Функціональний об'єкт порівняння витрат

class compareDates {

public:

bool operator () (expense*, expense*) const;

};

// Функціональний об'єкт порівняння витрат

class compareCategories {

public:

bool operator () (expense*, expense*) const;

};

class expenseRecord {

private:

// Вектор покажчиків на витрати

vector<expense*> vectPtrsExpenses;

vector<expense*>::iterator iter;

public:

~expenseRecord();

void insertExp(expense*);

void display();

float displaySummary(); // Потрібно для річного звіту

}; // <------> Кінець класу expenseRecord

class expenseInputScreen {

private:

expenseRecord* ptrExpenseRecord;

public:

expenseInputScreen(expenseRecord*);

void getExpense();

}; // <------> Кінець класу expenseInputScreen

class annualReport {

private:

rentRecord* ptrRR;

expenseRecord* ptrER;

float expenses, rents;

public:

annualReport(rentRecord*, expenseRecord*);

void display();

}; // <------> Кінець класу annualReport

class userInterface {

private:

tenantList* ptrTenantList;

tenantInputScreen* ptrTenantInputScreen;

rentRecord* ptrRentRecord;

rentInputScreen* ptrRentInputScreen;

expenseRecord* ptrExpenseRecord;

expenseInputScreen* ptrExpenseInputScreen;

annualReport* ptrAnnualReport;

char ch;

public:

userInterface();

~userInterface();

void interact();

}; // <------> Кінець класу userInterfac

// <------> Кінець файлу landlord.h

Оголошення класів

Оголошувати класи – це просто. Більшість оголошень зростають безпосередньо з класів, створених за допомогою узятих з описів варіантів використання іменників, і відображаються на діаграмі класів. Тільки імена потрібно зробити однослівними з багатослівних. Наприклад, ім'я "Перелік мешканців" (Tenant List) перетворюється в TenantList.

У заголовному файлі було додано ще декілька допоміжних класів. Згодом виявиться, що ми зберігаємо покажчики на об'єкти в різних типах контейнерів STL. Це означає, що ми повинні порівнювати об'єкти цих контейнерів так, як це описано в розд. 21 "Введення в стандартну бібліотеку шаблонів (STL)". Об'єктами порівняння насправді є класи compareTenants, compareRows, compareDates і compareCategories.

Описи атрибутів

Як вже було зазначено вище, багато атрибутів (методи) для кожного з класів є похідними з тих іменників, які самі не стали класами. Наприклад, name і aptNumber стали атрибутами класу tenant.

Інші атрибути можуть бути виведені з асоціацій в діаграмі класів. Асоціації можуть визначати ті атрибути, які є покажчиками або посиланнями на інші класи. Це пояснюється неможливістю асоціювати щось з чимось, що знаходиться невідомо де. Таким чином, у класі rentInputScreen з'являються атрибути ptrTenantList і ptrRentRecord.

Складені значення (агрегати)

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

Ні за описами варіантів використання, ні за діаграмами класів неможливо вгадати, якого роду контейнери повинні використовуватися для цих агрегатів. Вам як програмістам доведеться самим щоразу вибирати відповідний контейнер для кожного складеного значення – будь-то простий масив, контейнер STL або що-небудь ще. У програмі landlord ми зробили такий вибір:

  • клас tenantList містить STL-множину покажчиків на об'єкти класу tenant;

  • клас rentRecord містить множину покажчиків на об'єкти класу rentRow;

  • клас expenseRecord містить вектор покажчиків на об'єкти класу expense.

Для tenantList і rentRecord ми вибрали множини, оскільки основним параметром є швидкий доступ до даних. Для expenseRecord вибрані вектор, тому що нам важливо здійснювати швидке сортування і за датою, і за категоріями, а вектори дають змогу сортувати дані найефективніше.

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