Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Задача 1.1. Поиск в массиве структур



В текстовом файле хранится база отдела кадров предприятия. На предприятии

100 сотрудников. Каждая строка файла содержит запись об одном сотруднике.

Формат записи: фамилия и инициалы (30 позиций, фамилия должна начинаться с

первой позиции), год рождения (5 позиций), оклад (10 позиций). Написать программу,

которая по заданной фамилии выводит на экран сведения о сотруднике,

подсчитывая средний оклад всех запрошенных сотрудников.

Более подробно об этом — также на втором семинаре.

Задача 1.1. Поиск в массиве структур 17

Напомним, что в программе, предложенной для решения задачи 6.1 (П1)\

для хранения сведений об одном сотруднике была использована следующая

структура Man:

struct Man {

char name[l_name + 1]:

1nt birth_year;

float pay:

}:

Начнем с того, что преобразуем эту структуру в класс, так как мы предполагаем,

что наш новый тип будет обладать более сложным поведением, чем просто чтение

и запись его полей:

class Man {

char name[l_name + 1]:

1nt b1rth_year:

float pay;

}:

Замечательно. Это у нас здорово получилось! Все поля класса по умолчанию —

закрытые (private). Так что если клиентская функция ma1n() объявит объект Man

man, а потом попытается обратиться к какому-либо его полю, например: man.pay =

value, то компилятор быстро пресечет это безобразие, отказавшись компилировать

программу. Поэтому в состав класса надо добавить методы доступа к его полям.

Эти методы должны быть общедоступными, или открытыми (public).

Однако предварительно вглядимся внимательнее в определения полей. В решении

задачи 6.1 (П1) поле name объявлено как статический массив длиной l_name + 1.

Это не очень гибкое решение. Мы хотели бы, чтобы наш класс Man можно было

использовать в будущем в разных приложениях. Например, если предприятие

находится в России, то значение l_name = 30, по-видимому, всех устроит, если же

приложение создается для некой восточной страны, может потребоваться, скажем,

значение l_name = 200. Решение состоит в использовании динамического массива

символов с требуемой длиной. Поэтому заменим поле char named_name + 1]

на поле char* pName. Сразу возникает вопрос: кто и где будет выделять память

под этот массив? Вспомним один из принципов ООП: все объекты должны быть

самодостаточными, то есть полностью себя обслуживать.

Таким образом, в состав класса необходимо включить метод, который обеспечил

бы выделение памяти под указанный динамический массив при создании объекта

(перемершой типа Man). Метод, который автоматически вызывается при создании

экземпляра класса, называется конструктором. Компилятор безошибочно

находит этот метод среди прочих методов класса, поскольку его имя всегда совпадает

с именем класса.

Парным конструктору является другой метод, называемый деструктором, который

автоматически вызывается перед уничтожением объекта. Имя деструктора

Запись в скобках «П1» означает, что имеется в виду задача 6.1 из первой книги практикума.

1 8 Семинар 1. Классы

отличается от имени конструктора только наличием предваряющего символа -

(тильда).

Ясно, что если в конструкторе была выделена динамическая память, то в деструкторе

нужно побеспокоиться об ее освобождении. Напомним, что объект,

созданный как локальная переменная в некотором блоке { }, уничтожается, когда

при выполнении достигнут конец блока. Если же объект создан с помош; ью

операции new, например:

Man* рМап = new Man;

то для его уничтожения применяется операция delete, например: delete рМап.

Итак, наш класс принимает следующий вид:

class Man {

public:

Mandnt IName = 30) { pName = new char[lName + 1]; } // конструктор

-ManO { delete [] pName; } // деструктор

private:

char* pName:

1nt b1rth_year:

float pay;

}:

Обратим ваше внимание на одну синтаксическую деталь — объявление класса

должно обязательно завершаться точкой с запятой (; ). Если вы забудете это сделать,

то получите от компилятора длинный список маловразумительных сообщений

о чем угодно, но только не об истинной ошибке. Что поделаешь, таков уж

наш компилятор...__

 

Классы в объектно-ориентированных программах используются для моделирования

концепций реального и программного мира. Концепции (или сущности)

предметной области находятся в различных взаимоотношениях. Одно из таких

взаимоотношений — отношение наследования (именуемое также отношением ро-

дитель/потомок или отношением обобщение/специализация).

Например, если бы в условии задачи 1.2 содержалось требование обработки не

только треугольников, но и четырехугольников, то анализ нового класса Tetragon

выявил бы наличие общих черт с классом Тп angle. Причиной такой общности

является то, что и треугольники, и четырехугольники являются частным (специальным,

конкретным) случаем более общего понятия «многоугольник». Поэтому

было бы логичным создать класс Polygon, содержащий элементы, общие для

классов Triangle и Tetragon, а последние два класса объявить «наследниками»

базового (родительского) класса Polygon. Язык C++ позволяет легко это сделать:

class Polygon {

//...

};

class Triangle: public Polygon {

public:

ShowO;

Наследование классов 59

class Tetragon: public Polygon {

public:

ShowO:

}:

В этом примере производные классы Triangle и Tetragon наследуют все элементы

базового класса Polygon, но каждый из них имеет свой собственный метод Show().

Иногда отношение наследования называют отношением «Z5 а», что можно перевести

как «представляет собой». В данном примере Triangle is а Polygon, в такой

же мере и Tetragon is а Polygon.

Общий синтаксис создания производного класса при простом наследовании:

class имя: ключ_доступа имя_базового_класса {

/ / тело класса

}:

В случае множественного наследования после двоеточия перечисляются через запятую

все базовые классы со своими ключами (модификаторами) доступа.

Производный класс, в свою очередь, сам может служить базовым классом. Такой

набор связанных классов традиционно называется иерархией классов. Иерархия

чаще всего является деревом, но может иметь и более общую структуру графа.

В примерах предыдущего семинара доступ к элементам класса регулировался

с помощью двух модификаторов (спецификаторов): private — закрытая часть

класса, public — открытая часть класса. Для базовых классов возможно использование

еще одного модификатора — protected, который определяет так называемую

защищенную часть класса. Смысл «защиты» заключается в том, что элементы

этой части класса являются доступными для любого производного класса, но

в то же время они недоступны извне классов данной иерархии.

Кроме этого, доступность в производном классе регулируется ключом доступа

к базовому классу, указываемому в объявлении производного класса. Этот ключ

определяет вид наследования: открытое (public), защищенное (protected) или закрытое

(private).

Открытое наследование сохраняет статус доступа всех элементов базового класса,

защищенное — понижает статус доступа public элементов базового класса до

protected, и наконец, закрытое — понижает статусы доступа public и protected

элементов базового класса до private. Заметим, что в C++ отношение между

классами «/5 а» имеет место только при открытом наследовании.__

 

****(Для некоторого М7южества заданных координатами своих вершин треугольников

найти треугольник максимальной площади (если максимальную площадь имеют

несколько треугольников, то найти первый из них). Предусмотреть возможность

перемещения треугольников и проверки включения одного треугольника в другой.

Для реализации этой задачи составить описание класса треугольников на плоскости.

Предусмотреть возможность объявления в клиентской программе (main)

экземпляра треугольника с заданными координатами вершин. Предусмотреть

наличие в классе методов, обеспечивающих: 1) перемещение треугольников на

плоскости; 2) определение отношения > для пары заданных треугольников (мера

сравнения — площадь треугольников); 3) определение отношения включения

типа: «Треугольник 1 входит в (не входит в) Треугольник 2».

Программа должна содержать меню, позволяющее осуществить проверку всех

методов класса.

Отметим, что сейчас мы еще не готовы провести полномасштабную объектно-

ориентированную декомпозицию программы. Для этого нам не хватает знаний,

которые будут получены на втором семинаре. Поэтому применим гибридный

подход: разработку главного клиента mainO проведем по технологии функциональной

декомпозиции, а функции-серверы, вызываемые из mainO, будут использовать

объекты.

Начнем с выявления основных понятий/классов для нашей задачи. Первый очевидный

класс Triangle необходим для представления треугольников. Из нескольких

способов определения треугольника на плоскости выберем самый простой —

через три точки, задающие его вершины. Сделанный выбор опирается на новое

понятие, отсутствующее в условии задачи, — понятие точки. Точку на плоскости

можно представить различными способами; остановимся на наиболее популярном

— с помощью пары вещественных чисел, задающих координаты точки

по о с я м XVI у.

Таким образом, с понятием точки связывается как минимум пара атрибутов.

В принципе, этого уже достаточно, чтобы подумать о создании класса Point.

Если же представить, что можно делать с объектом типа точки — например,

перемещать ее на плоскости или определять ее вхождение в заданную фигуру, —

то становится ясным, что такой класс Point будет полезен.

Итак, объектно-ориентированная декомпозиция дала нам два класса: Triangle

и Point. Как эти два класса должны быть связаны друг с другом? На втором

3 4 Семинар 1. Классы

семинаре мы будем более подробно рассматривать взаимоотношения между

классами. Пока же отметим, что наиболее часто для двух классов имеет место

одно из двух:

• отношение наследования (отношение is а);

• отношение агрегации или включения (отношение has а).

Если класс В является «частным случаем» класса А, то говорят, что Bis а /\ (например,

класс треугольников есть частный вид класса многоугольников: Triangle is а

Polygon).

Если класс А содержит в себе объект класса В, то говорят, что А has а В (например,

класс треугольников может содержать в себе объекты класса точек: Triangle has а

Point).

Теперь уточним один вопрос: в каком порядке мы будем перечислять точки, задавая

треугольник? Порядок перечисления вершин важен для нас потому, что в дальнейшем,

решая подзадачу определения отношения включения одного треугольника

в другой, мы будем рассматривать стороны треугольника как векторы. Условимся,

что вершины треугольника перечисляются в направлении по часовой стрелке.

Займемся теперь основным клиентом — mainO. Здесь мы применяем функциональную

декомпозицию, или технологию нисходящего проектирования. В соответствии

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

нескольких подзадач. Каждой подзадаче соответствует вызываемая

серверная функция. На начальном этапе проектирования тела этих функций могут

быть заполнены «заглушками» — отладочной печатью. Если при этом в какой-

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

разбивается на несколько подзадач.

То же самое происходит и с классами, используемыми в программе: по мере реализации

подзадач они пополняются необходимыми для этого методами. Такая

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

программы.)********

 

ЧТО ТАКОЕ ПОЛИМОРФИЗМ

Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Предположим, например, что вы программист, работающий в телефонной компании, и вам необходимо написать программу, которая эмулирует телефонные операции. Поскольку вы знакомы с тем, как люди используют телефон, вы быстро выберете общие операции, такие как набор номера, звонок, разъединение и индикация занятости. С помощью этих операций вы определите следующий класс:

class phone

{
public:
void dial(char " number) { cout < < " Набор номера " < < number < < endl; }
void answer(void) { cout < < " Ожидание ответа" < < endl; }
void hangup(void) { cout < < " Звонок выполнен - повесить трубку" < < endl; }
void ring(void) { cout < < " Звонок, звонок, звонок" < < endl; )
phone(char *number) { strcpy(phone:: number, number); };
private:
char number[13];
);

Следующая программа PHONEONE.CPP использует класс phone для создания объекта-телефона:

#include < iostream.h>

#include < string.h>

class phone

{
public:
void dial(char *number) { cout < < " Набор номера " < < number < < endl; }
void answer(void) { cout < < " Ожидание ответа" < < endl; }
void hangup(void) { cout < < " Звонок выполнен - повесить трубку" < < endl; }
void ring(void) { cout < < " Звонок, звонок, звонок" < < endl; }
phone(char *number) { strcpy(phone:: number, number); };
private:
char number[13];
};

void main(void)

{
phone telephone(" 555-1212" );
telephone.dial(" 212-555-1212" );
}

Если вы продемонстрируете программу вашему боссу, то он или она скажет, что ваша программа не делает различий между дисковым и кнопочным телефонами, и что она не поддерживает платные телефоны, когда пользователь должен заплатить 25 центов, чтобы позвонить.

Поскольку вы знаете наследование, то примете решение породить классы touch_tone иpay_phone из класса phone, как показано ниже:

class touch_tone: phone

{
public:
void dial(char * number) { cout < < " Пик пик Набор номера " < < number < < endl; }
touch_tone(char *number): phone(number) { }
};

class pay_phone: phone

{
public:
void dial(char * number)

{
cout < < " Пожалуйста, оплатите " < < amount < < " центов" < < endl;
cout < < " Набор номера " < < number < < endl;
}

pay_phone(char *number, int amount): phone(number) { pay_phone:: amount = amount; }
private:
int amount;
};

Как видите, классы touch_tone и pay__phone определяют свой собственный метод dial. Если вы предположите, что метод dial класса, phone основан на дисковом телефоне, то вам не потребуется создавать класс для дискового телефона. Следующая программа NEWPHONE.CPP использует эти классы для создания объектов rotary, touch_tone иpay_phone:

#include < iostream.h>

#include < string.h>

class phone

{
public:
void dial(char *number) { cout < < " Набор номера " < < number < < endl; }
void answer(void) { cout < < " Ожидание ответа" < < endl; }
void hangup(void) { cout < < " Звонок выполнен - повесить трубку" < < endl; }
void ring(void) { cout < < " Звонок, звонок, звонок" < < endl; }
phone(char *number) { strcpy(phone:: number, number); };
protected:
char number[13];
);

class touch_tone: phone

{
public:
void dial(char *number) { cout < < " Пик пик Набор номера " < < number < < endl; }
touch_tone(char *number): phone(number) { }
};

class pay_phone: phone

{
public:
void dial(char * number) { cout < < " Пожалуйста, оплатите " < < amount < < " центов" < < endl; cout < < " Набор номера " < < number < < endl; }
pay_phone(char * number, int amount): phone(number) { pay_phone:: amount = amount; }
private:
int amount;
};

void main (void)

{
phone rotary(" 303-555-1212" );
rotary.dial(" 602-555-1212" );
touch_tone telephone(" 555-1212" );
telephone.dial(" 212-555-1212" );
pay_phone city_phone(" 555-1111", 25);
city_phone.dial(" 212-555-1212" );
}

Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод:

С: > NEWPHONE < Enter>

Набор номера 602-555-1212


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-07-14; Просмотров: 458; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.071 с.)
Главная | Случайная страница | Обратная связь