Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву
Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Глава 6. Основы объектно-ориентированного программированияСодержание книги
Поиск на нашем сайте В предыдущих примерах программы представляли собой набор функций и директив, идущих друг за другом в определенном порядке. Это структурный подход к программированию. Ему присущи некоторые недостатки, связанные с формализацией задач программирования. Такие задачи, как правило, связаны с манипулированием разнородных объектов. Например, создается графический редактор, где необходимо выполнить работу с графическими примитивами: линией, эллипсом и прямоугольником. При этом в соответствующих функциях программы возникает необходимость перебирать типы объектов, например, для их добавления, редактирования и отображения на экране. В результате получается громоздкий текст программы, в котором многократно описываются одни и те же действия. Теперь представим, что в рамках данной задачи общее число возможных графических элементов не три, а сто. В этом случае получится еще более сложная программа, в которой станет сложно ориентироваться. Кроме того, на практике часто наперед неизвестно общее число возможных вариантов (типов графических объектов) и исправление текста программы для добавления возможности работы с новым объектом становится трудоемкой задачей для программиста. Представленный пример создания графического редактора показывает лишь один из недостатков структурного программирования. В общем случае существует большое множество разнообразных задач плохо поддающихся описанию, вследствие чего и возникла необходимость разработки нового подхода к программированию, которым стало объектно-ориентированное программирование (ООП). 6.1. Понятие классов в С++ Идея ООП заключается в описании задачи на уровне объектов, которые в языке С++ называются классами. Например, класс может описывать объект линию, эллипс, прямоугольник. Но в отличие от структур, которые также могут комплексно описывать свойства каких-либо объектов, между классами возможны взаимодействия, которые выражаются тремя категориями: наследование, полиморфизм и инкапсуляция. Наследование – это механизм создания нового класса на основе ранее созданного. Наследование имеет смысл, если множество разнородных объектов имеют общие характеристики или функции. Так, в случае с графическими примитивами тип линии, цвет и толщина описываются одинаково на уровне языка программирования и логически отностятся к одной категории – свойства графического примитива. Поэтому эти элементы целесообразно выделить в отдельный класс – базовый и на основе него создавать новые классы – дочерние для более детального описания линии, эллипса и прямоугольника, используя механизм наследования. Полиморфизм – это процесс вызова и переопределение функций базового класса в дочерних. Полиморфизм позволяет общие функции дочерних классов выносить в базовый, а затем их вызывать из дочернего, полагая, что они определены в нем. Вместе с тем это не исключает возможности переопределения функций базового класса в дочерних. Инкапсуляция – это способ представления класса в виде «черного ящика». Это значит, что конечному пользователю класса (программисту) доступен лишь определенный набор функций и переменных для работы с классом. Часто ограничение доступа применяется для записи значений в переменные класса через функции, при запрещенном непосредственном доступе к переменным. Класс в языке С++ задается с помощью ключевого слова class, за которым следует его имя и в фигурных скобках {} дается его описание. После определения класса ставится точка с запятой. Ниже приведен пример описания класса для хранения координат графических примитивов: class CPos Каждый класс имеет специальные функции, которые называются конструктор и деструктор. Конструктор класса вызывается всякий раз, когда объект создается в памяти ЭВМ и служит обычно для инициализации данных класса. Конструктор имеет то же имя, что и имя класса. Деструктор вызывается при удалении класса из памяти и используется, как правило, для освобождения ранее выделенной памяти под какие-либо данные этого класса. Имя деструктора совпадает с именем класса, но перед ним ставится символ ‘~’. Рассмотрим пример реализации конструктора и деструктора для класса CPos. class CPos int sp_x, sp_y; //координата начала Здесь ключевое слово public используется для обеспечения общего доступа к функциям и переменным класса. Для создания нового экземпляра класса в памяти ЭВМ используется оператор new языка С++, а для удаления – оператор delete. Использование данных операторов для создания экземпляра класса CPos и его удаления выглядит следующим образом: CPos *pos_ptr = new CPos(); //создание объекта В результате выполнения этих двух строк программы на экране появятся сообщения: Вызов конструктора. Экземпляр класса также можно создать подобно обычным переменным без использования указателей, как показано ниже CPos pos; В этом случае переменная pos называется представителем класса, у которого также вызывается конструктор при его создании и деструктор при его удалении из памяти. Следует отметить, что при создании нового экземпляра класса можно выполнять инициализацию различных переменных путем передачи их значений через конструктор. В этом случае конструктор должен быть объявлен с набором необходимых аргументов, например, так: class CPos int sp_x, sp_y; и процесс создания экземпляра класса принимает вид: CPos *pos_ptr = new CPos(10,10,20,20); или CPos pos(10,10,20,20); Такой способ описания и вызова конструктора представляет дополнительное удобство инициализации данных при создании нового объекта. При этом конструктор, как и любую функцию, можно перегружать. Это значит, что можно задать несколько типов конструкторов (с разным набором входных параметров) в одном и том же классе. Например, если создается экземпляр класса графического примитива, но для него неизвестны начальные и конечные координаты, то целесообразно вызвать конструктор CPos() без аргументов, а если координаты известны, то выполнить их инициализацию путем вызова конструктора с аргументами. Для описания нескольких типов конструкторов в одном классе достаточно дать их определения в нем: class CPos int sp_x, sp_y; В классах помимо переменных, конструкторов и деструкторов можно задавать описания и обычных функций, которые, в этом случае, называются методами. Например, в классе CPos для задания значений координат примитива целесообразно добавить функцию для ввода значений в переменные sp_x, sp_y, ep_x и ep_y. Это позволит, во-первых, не запоминать программисту имена этих переменных, а оперировать только одной функцией и, во-вторых, в самой функции можно реализовать необходимые проверки на истинность переданных значений координат перед их присваиванием переменным. Такую функцию можно описать в классе следующим образом: class CPos void SetParam(int x1, int y1, int x2, int y2) int sp_x, sp_y; В приведенном примере реализована функция SetParam(), которая перед присваиванием значений переменных выполняет проверку на их истинность. Здесь некоторое неудобство представляет то, что данная функция полностью описана в классе CPos, а описание большого числа функций в одном классе делает текст программы трудночитаемым. Поэтому обычно в классах записывают лишь прототипы функций, а их реализации приводят отдельно после описания класса. Для того чтобы описать реализацию функции SetParam() вне класса CPos перед именем функции ставится имя класса с оператором глобального разрешения ‘::’ как показано ниже: void CPos::SetParam(int x1, int y1, int x2, int y2) а перед ней должно идти следующее определение класса: class CPos void SetParam(int x1, int y1, int x2, int y2); int sp_x, sp_y; Аналогичным образом можно давать описание конструкторов и деструкторов за пределами класса. Учитывая, что данные функции ничего не возвращают вызывающей программе и не имеют типов, то их внешняя реализация будет иметь вид: CPos::CPos() CPos::~CPos() Функцию SetParam() можно вызывать через указатель на класс, используя оператор ‘->’ или через представитель с помощью оператора ‘.’: CPos* pos_ptr = new CPos(); pos_ptr->SetParam(10,10,20,20); Таким же образом можно обращаться и к переменным класса: pos_ptr->sp_x = 10; Здесь можно заметить, что значения переменных sp_x, sp_y, ep_x и ep_y могут быть заданы как непосредственно при обращении к ним, так и с помощью функции SetParam(). В результате проверка, реализованная в данной функции, может быть проигнорирована программистом. Часто такая ситуация недопустима, например, при использовании готовых классов библиотек MFC, VCL, OWL и др. В связи с этим в классах для переменных и функций предусмотрена возможность установки разных уровней доступа, которые определяются тремя ключевыми словами: public, private и protected. Ключевое слово public означает общий доступ к переменным и функциям класса. Уровень доступа private указывает на частный способ доступа к элементам класса и устанавливается по умолчанию при описании класса. Частный уровень доступа дает возможность обращаться к переменным и функциям только внутри класса и запрещает извне, например, через представители или указатели на класс. Режим доступа protected также как и private запрещает доступ к элементам класса через представители и указатели, но разрешает обращаться к ним из дочерних классов при наследовании. Учитывая эти три режима доступа, класс для работы с координатами графических объектов целесообразно записать в таком виде: class CPos void SetParam(int x1, int y1, int x2, int y2); private: Здесь раздел private ограничивает доступ пользователю класса к переменным sp_x, sp_y, ep_x и ep_y только функцией SetParam(). Следует также отметить, что отсутствие раздела public вначале описания класса привело бы к тому, что все функции класса CPos имели бы область видимости private. В результате доступ к конструктору и деструктору был бы запрещен, и создание нового объекта стало бы невозможным. Аналогичная картина имеет место и в режиме доступа protected, но в отличие от private класс можно использовать как базовый в механизме наследования. Это свойство полезно использовать для запрета создания экземпляров класса, что бывает необходимым, если он является лишь промежуточным звеном в иерархии объектов и не представляет ценности как отдельный объект. Наследование Рассмотренный класс CPos не описывает особенностей работы с конкретными графическими примитивами: линией, прямоугольником, эллипсом, а содержит лишь общую для них информацию. Поэтому данный класс следует рассматривать как базовый, на основе которого можно построить дочерние для более детальной работы с графическими объектами, используя механизм наследования. Предположим, создается дочерний класс с именем CLine для работы с линией на основе базового CPos. Для этого после имени дочернего класса CLine ставится символ ‘:’, а затем пишется имя базового класса CPos с указанием уровня доступа: class CPos void SetParam(int x1, int y1, int x2, int y2); protected: class CLine: public CPos void Draw() {MoveTo(sp_x,sp_y); LineTo(ep_x,ep_y);} В результате наследования с уровнем доступа public класс CLine имеет доступ ко всем переменным и функциям класса CPos, которые не являются частными (private). Ключевое слово public перед именем класса CPos означает, что все общие (public) элементы этого класса остаются с таким же уровнем доступа и в классе CLine. Следует также отметить, что описание класса CPos должно предшествовать описанию класса CLine, а переменные sp_x, sp_y, ep_x и ep_y должны быть описаны в разделе protected для возможности их использования в функции Draw() дочернего класса CLine и в то же время не доступными извне. Класс CLine содержит два конструктора, деструктор и функцию Draw() для рисования линии на экране. При этом процедура задания координат графического объекта целиком находится в базовом классе CPos и по мере необходимости используется в дочернем классе CLine. Такое разделение оказывается удобным, т.к. при описании работы с новыми графическими объектами процедура работы с их координатами будет оставаться одной и той же и находится в одном классе. Если бы в данном случае использовался структурный подход к программированию, то алгоритм работы с координатами графических примитивов пришлось бы прописывать каждый раз для всех типов объектов, что привело бы к заметному усложнению текста программы. Для работы с дочерним классом, также как и с обычным, необходимо создать его экземпляр либо с помощью оператора new, либо через представитель, как показано ниже: CLine* line_ptr = new CLine(); или CLine line; При создании нового объекта CLine вызывается сначала конструктор CPos() базового класса, а затем конструктор дочернего – CLine(). Таким образом, создается как бы два объекта: CPos и CLine, но они представляются как единое целое объекта CLine. В представленном классе CLine предусмотрено два конструктора: с параметрами и без них. В случае вызова конструктора с параметрами CLine line(10,10,20,20); вызывается конструктор CPos() базового класса, а затем конструктор CLine(int x1, int y1, int x2, int y2) дочернего, в котором выполняется функция SetParam() для записи значений координат графического объекта. Последние два приведенных примера создания объекта CLine показывают, что вне зависимости от типа вызываемого конструктора дочернего класса всегда вызывается один и тот же конструктор CPos() базового класса, даже если в последнем определено несколько конструкторов. Это не всегда удобно и кроме того, если конструктор CPos() не описан в базовом классе, то создание дочернего класса CLine станет невозможным, т.к. конструктор по умолчанию CPos() не будет найден. Для того чтобы исправить такую ситуацию необходимо в дочернем классе указать, какой именно конструктор базового класса следует вызывать, следующим образом: class CLine: public CPos void Draw() {MoveTo(sp_x,sp_y); LineTo(ep_x,ep_y);} В приведенном примере конструктор CLine() будет вызывать конструктор CPos() базового класса, а конструктор CLine(int x1, int y1, int x2, int y2) конструктор CPos(int x1, int y1, int x2, int y2). При этом функция SetParam() в CLine(int x1, int y1, int x2, int y2) может быть опущена, т.к. необходимая инициализация переменных будет выполнена при вызове конструктора CPos(int x1, int y1, int x2, int y2) базового класса. В рассматриваемой задаче программирования графического редактора, класс CPos является вспомогательным, т.е. он служит для создания описания новых классов как базовый. При этом нет необходимости создавать его экземпляр в памяти ЭВМ для непосредственной работы с ним. Поэтому целесообразно защитить его от возможности создания путем помещения конструкторов данного класса в раздел protected. Такие классы называются абстрактными, т.е. они не могут существовать как самостоятельные объекты, а служат для создания новых, дочерних классов. Описание абстактного класса CPos и дочернего от него CLine показано ниже: class CPos public: protected: Функции классов CPos и CLine можно вызывать, например, через представитель класса CLine, следующим образом: CLine line; Обратите внимание, что благодаря полиморфизму, функция SetParam(), заданная в классе CPos, вызывается через представитель line как будто она определена в классе CLine. В результате, единожды объявленная функция SetParam() может быть многократно использована в разных классах, производных от CPos. Для работы с другими графическими примитивами (прямоугольником и эллипсом) подобным образом можно создать дочерние классы от CPos, отличающихся друг от друга реализацией функции Draw(): class CRect: public CPos void Draw() {Rectangle(sp_x,sp_y,ep_x,ep_y);} class CEllipse: public CPos void Draw() {Ellipse(sp_x,sp_y,ep_x,ep_y);} В результате построения объектов получается следующая иерархия (рис. 6.1).
Рис. 6.1. Иерархия классов графических примитивов У каждого из представленных дочерних объектов CLine, CRect и CEllipse имеется один базовый объект CPos. Вместе с тем язык С++ предоставляет возможность создавать дочерние объекты на основе нескольких базовых, что приводит к концепции множественного наследования. В рамках данной задачи множественное наследование будет иметь смысл, если добавить еще один абстрактный класс с именем CProp, который будет отвечать за свойства графических примитивов: толщина и цвет линии: class CProp public: Теперь дочерние классы CLine, CRect и CEllipse можно образовывать от двух базовых CPos и CProp, которые являются не связанными друг с другом. Для того чтобы построить класс на основе двух базовых они указываются друг за другом через запятую следующим образом: class CLine: public CPos, public CProp void Draw() {SetWidth(width); SetColor(color); Аналогичным образом строятся классы CRect и CEllipse. Здесь следует отметить, что конструктор CLine(int x1, int y1, int x2, int y2, int w, int clr) класса CLine вызывает конструкторы двух базовых классов, которые перечислены через запятую с указанием в них конкретных переменных. Работа с функциями класса CLine через его представитель имеет следующий вид: CLine line; Благодаря полиморфизму, функции SetProperty() и SetParam() базовых классов вызываются непосредственно из класса CLine.
|
||
|
Последнее изменение этой страницы: 2017-02-05; просмотров: 387; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 216.73.217.21 (0.009 с.) |