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

4966

.pdf
Скачиваний:
3
Добавлен:
13.11.2022
Размер:
854.74 Кб
Скачать

41

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

Для реализации схем доступа к элементом класса в интерфейсе класса применяются соответствующие атрибуты доступа: private, public, protected. Эти атрибуты следует ставить перед элементами (или группами элементов) в классе (в языке C++). При этом: private – указывает на закрытость элемента; public – указывает на открытость элемента; protected – указывает на доступность элемента в производных классах.

Смысл атрибутов доступа следующий:

- private – член класса с атрибутом private может использоваться только методами собственного класса и функциями-"друзьями" этого же класса; - protected – то же, что и private, но дополнительно член класса может

использоваться методами и функциями, а также “друзьями" производного класса, для которого данный класс является базовым;

- public – член класса может использоваться любой функцией программы, т.е. защита на доступ снимается.

Примечание: «друзьями» класса называются функции или другие классы; цель внесения в класс «дружественных» элементов – расширение видимости закрытых элементов класса для таких «друзей», при этом «дружественность» не наследуется; для выделения «друзей» применяется ключевое слово friend.

Принцип размещения описания интерфейса класса идентичен правилу описания интерфейса функций в языке Си. Приведём пример состава программного модуля в случае инкапсуляции некоторого класса:

1)начало программного модуля;

2)объявление класса;

3)определение функций класса;

4)окончание программного модуля.

Разработанный подобным образом программный модуль при необходимости создания экземпляра инкапсулированного класса (т.е. объекта класса) может быть использован в другом модуле посредством директивы #include.

Если классы – это группы однородных объектов, то группы классов – есть совокупность классов, объединённых по принципу общего использования элементов, принадлежащих определённым классам в группе (своеобразная «общественная собственность элементов» в группе классов). В случае использования

42

несколькими классами содержимого одного класса из группы говорят о «наследовании» содержимого такого класса (т.е. его данных и методов) другими классами группы. Класс, который предоставляет свои элементы для общего использования в группе, называется «базовым»; класс, который использует чужые элементы, называется «производным». Основная цель наследования – перенести базовые значения или характеристики в вершину иерархии классов и сделать эти характеристики достоянием всех последующих классов и экземпляров классов в иерархии. Этот принцип позволяет присоединять новую реализацию функций, инкапсулированных в базовом классе, на этапе дальнейшей разработки программного кода. Таким образом, каждая стадия разработки проекта на очередном итерационном шаге и представленная в виде артефакта – программного кода, может быть откомпилирована, отлажена и скомпонована в рабочий исполняемый программный комплекс. При этом добавление новых функциональных возможностей, спроектированных и разработанных на новой стадии проекта, осуществляется без перекомпиляции ранее отлаженного кода путём наращивания новых возможностей на имеющееся базовое ядро проектируемой информационной системы.

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

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

В ООП (объектно-ориентированная парадигма) имеется два основных вида наследования одиночное и множественное. Если класс наследует свойства или функции более чем у одного базового класса, то говорят о множественном наследовании. В противном случае имеет место одиночное наследование. Пример разновидностей одиночного наследования приведён на рисунке 9.

Пример разновидностей множественного и одиночного наследования приведён на рисунке 10.

//cl1 имя базового класса //atr1 атрибут наследования
//private, public, protected // скобки [ ] означают //«не обязательно».

 

43

 

 

а)

б)

в)

 

Базовый класс

Базовый класс

Базовый класс

А

В

С

 

Производный

Производный

Производный

Производный

 

 

 

класс АА

класс ВВ

класс А

класс В

 

 

 

 

Производный

Производный

Производный

 

класс ВВВ

класс АА

класс ВВ

Схемы наследования:

 

 

а) явное одиночное наследование;

 

 

б) неявное одиночное наследование;

 

 

в) неявное одиночное наследование;

 

 

Рисунок 9 – Примеры одиночного наследования в иерархии классов

Пример описания иерархии наследования на языке С++ (см. рисунок 9): class AA: public A{}; // одиночное наследование, схема а

class ВВ: public В{}; // одиночное наследование, схема б class ВBВ: public ВВ{}; // одиночное наследование, схема б class А: public С{}; // одиночное наследование, схема в.

Общая схема синтаксических элементов описания наследуемости в иерархии посредством языка С++:

class cl3: atr1 cl1[, atr2 cl2]{};

Атрибут наследования осуществляет управление доступом к элементам базового класса (наследуемым элементам) внутри производного класса и влияет на дальнейшее наследование таких элементов.

Схема разграничения и управления доступом в производном классе к наследуемым элементам приведена в таблице 1.

44

Таблица 1Схема разграничения и управления доступом при наследовании

Значение атрибута

Значение

атрибута

Доступ в производном классе к

наследования

доступа в

базовом

наследуемым элементам

 

классе

 

 

Private

private

 

доступа нет

 

protected

 

доступ есть

 

public

 

доступ есть

Protected

Private

 

доступа нет

 

protected

 

доступ есть

 

public

 

доступ есть

Public

Private

 

доступа нет

 

protected

 

доступ есть

 

public

 

доступ есть

С одной стороны, объектно-ориентированный язык С++, как и любой другой язык программирования, имеет свои, подчас жёсткие правила организации вычислений. С другой стороны, этот язык, как никакой другой, требует понимания объектно-ориентированной идеи. В противоположность процедурным языкам, которые позволительно использовать, освоив лишь начала алгоритмизации и выучив некоторый минимум операторов, создавать объектно-ориентированные программы на объектно-ориентированном С++ без предварительного проектирования (в т.ч. и иерархии наследуемости) классов невозможно. Язык С++ является лишь хорошим средством реализации проекта наследования и разграничения доступа к наследуемым элементам в иерархии классов.

Стимулом к изучению языка С++, как средства реализации объектноориентированной парадигмы, является возможность применения мощных библиотек классов MFC (Microsoft Foundation Classes) при осуществлении программных разработок в различных средах программирования (Visual Basic, ASP.NET, Microsoft Visual Studio и т.д.). Следует упомянуть также среду разработки (конфигуратор) модулей популярной системы 1С, в которой в качестве средства программирования применяется предметно-ориентированный язык, построенный на объектно-ориентированных принципах.

45

а)

 

Базовый класс

 

Базовый класс

 

 

А

 

В

 

 

 

 

 

 

 

 

Производный класс АВ

б)

 

 

 

Базовый

 

 

Базовый

 

 

 

 

 

класс А

 

 

класс В

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Производный

 

 

Производный

 

 

 

 

 

класс АА

 

 

класс ВВ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Производный

 

 

 

 

 

 

 

класс АВС

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

в)

 

 

 

Базовый

 

 

 

 

 

 

 

 

 

класс А

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Производный

 

 

 

Производный

 

 

 

 

 

класс АА

 

 

класс ВВ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Производный класс АВС

Схемы наследования:

а) явное множественное наследование; б) неявное одиночное и множественное наследование;

в) неявное одиночное и множественное наследование.

Рисунок 10 – Примеры множественного и одиночного наследования в иерархии классов

46

В завершение этого раздела о наследовании, не претендующего на исчерпывающую полноту, но достаточного для целей постановки задачи на разработку объектно-ориентированного проекта, приведём некоторые правила работы с иерархическими схемами наследования:

1)при создании объекта производного класса объём резервируемой памяти соответствует размеру всех наследуемых элементов в базовых классах;

2)при создании объекта производного класса в случае множественного наследования объём резервируемой памяти может увеличиваться многократно по «ветвям» наследования (см. схему в на рисунке 10 );

3)при наличии одноимённых элементов в производных и базовых классах следует применять оператор расширения видимости «::» (в С++) для указания принадлежности таких одноимённых элементов;

4)функции и методы, принадлежащие базовым классам, могут быть переопределены (перегружены) в производных классах на основании принципа полиморфизма.

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

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

Так как за создание объекта (его конструирование по заданному сценарию) несут ответственность методы класса, то их принято именовать «конструкторами». В литературе часто встречается словосочетание «конструктор класса», это выражение, на наш взгляд, не совсем точно передаёт суть, так как конструкторы не конструируют классы. Выражение «конструктор класса» может ввести неис-

47

кушённого читателя в заблуждение, однако его следует понимать так: «конструктор, принадлежащий такому-то классу».

2.2.5. Конструкторы

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

Перечислим основные свойства конструкторов:

1)имеет интерфейс, аналогичный прототипу обычной функции, при этом имя конструктора совпадает с именем класса;

2)не имеет возвращаемого значения;

3)автоматически вызывается при объявлении объекта класса;

4)не может иметь модификаторы памяти const, static, virtual.

5)применяется для инициализации элементов-данных класса.

Конструкторы и наследование

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

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

48

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

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

Class cl3:: public cl1, public cl2{};

//иерархия наследования

cl3(): cl1(),cl2() {...};

//прототип конструктора класса cl3.

Конструкторы в иерархии классов должны выполняться в следующем порядке: первыми будут выполняться конструкторы базового класса, затем − производного класса.

Деструкторы

Деструкторы выполняют предписанные действия по уничтожению созданных объектов (т.е. очистке занимаемой памяти или других необходимых операций, связанных с завершением жизни объектов). Например, освобождение памяти и уничтожение указателя бывает необходимо в случаях применения нескольких указателей на адреса зарезервированной или(и) занятой памяти.

Процесс вызова деструкторов аналогичен неявному вызову конструкторов, т.е. он выполняется в любом случае как при наличии описания прототипа деструктора, так и без оного. Вызываться деструкторы будут в автоматическом порядке для каждого созданного объекта при завершении работы программного модуля. В случае описания каких-либо операторов при определении конструктора произойдёт их выполнение, в противном случае – не произойдёт никаких действий. Это действие (или бездействие) будет применено ко всем объектам, созданным на базе данного класса в исполняемом программном модуле. При описании класса (т.е. при инкапсуляции) конструктор и деструктор имеют следующие различия:

class ABC // объявление абстрактного типа АВС { ABC(); //конструктор класса АВС

~ABC(); //деструктор класса АВС };

Деструкторы выполняются в порядке, обратном конструкторам. Деструктору производного класса не требуется явно вызывать деструктор базового класса. Компилятор автоматически генерирует вызов базового деструктора. Деструктор не может иметь аргументы или возвращать значение, наследоваться, объявляться как const или static.

49

Конструкторы: виды и назначение

Явные конструкторы

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

С параметрами

Для инициализации переменных класса значениями формальных параметров при создании объекта (в момент передачи сообщения)

НЕ явные конструкторы (без инициализации переменных), создаются компилятором, вызываются автоматически при создании объекта, резервируют память под объект

Без параметров

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

 

 

Инициализация

 

Инициализация с

 

Инициализация

 

 

 

формальных параметров

 

явными операторами

 

копированием

 

 

 

через список

 

присваивания

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Class ABC1

class ABC2

class ABC3

 

 

{

{

 

{

 

 

 

private: int x,y;

private: int x,y;

private: int x,y;

 

 

public: ABC1(int a, int b);

public: ABC2(int a, int b);

public: ABC3(int a, int b);

 

 

};

};

};

 

 

 

//реализация метода:

//реализация метода:

//реализация метода:

 

 

ABC1::ABC1(int a,int

ABC2::ABC2(int a,int b)

ABC3::ABC3(int a,int b)

 

 

b):x(a),y(b)

{x=a; y=b;}

{x=a; y=b;}

 

 

{cout<<"x="<<x<<endl;

 

 

 

 

 

 

 

cout<<"y="<<y<<endl;

 

 

 

 

 

 

}

 

 

 

 

 

 

//x=a y=b

void main(void)

void main (void)

 

 

void main(void)

{ABC2 abc2(2,4);}

{ ABC3 abc3(1,2);

 

 

{ABC1 abc(2,4);}

//создание

ABC3 cde=abc3;}

 

 

//создание

// объекта с

//результат:

 

 

// объекта с

//передачей

//cde.x=1;

 

 

//передачей

//сообщения

//cde.y=2

 

 

//сообщения

 

 

 

 

 

 

 

 

 

 

 

 

 

Рисунок 11 – Разновидности и назначение конструкторов

50

2.2.6.Разновидности полиморфизма

Сцелью использования принципов наследования наиболее общие для всей иерархии данные, методы и функции удобно описывать на самых верхних уровнях иерархии, т.е. в базовом классе. При этом методы и функции, объявленные в базовом классе могут быть переопределены по мере необходимости в классах наследниках без повторной компиляции программного модуля, в котором инкапсулирован базовый класс. Например, в случае описания в интерфейсе класса некоторого метода(функции) в виде прототипа (т.е. имеет место описание имени метода(функции) и перечня формальных параметров) мы имеем возможность определить код реализации метода(функции) позднее, т.е. в другом программном модуле, воспользовавшись оператором расширения видимости: «имя_класса::имя_метода (параметры) {код метода}». Такой способ переопределения метода(функции) называется перегрузкой метода(функции). Для использования такого перегруженного метода(функции) необходимо точно указывать пространство имён, т.е. определить принадлежность данного перегружаемого метода определённому классу. Такая перегрузка потребует отрытого доступа к элементам базового класса, что не всегда бывает удобным и даже возможным, так как элементы класса могут быть закрыты от доступа. Однако, будучи единожды определённой, такая функция(метод) будет действительна для всех наследников в иерархии и не сможет полиморфно отражать изменчивость поведения в производных классах. Вызовы таких функций требуют определения связей на этапе компиляции, что известно как метод раннего (статического) связывания. Накладываемые таким связыванием ограничения не позволяют пользователю легко добавлять новые образцы классов и расширять свою программу без перекомпиляции работающих модулей.

Например, нельзя однозначно определить метод «перемещается» для класса «млекопитающие». Для производных классов возможны полиморфные изменения методов, отражающих способы перемещения объектов, например: летает, прыгает, плавает, ходит и т.д.

Например, нельзя одной функцией определить способ показа свойств у разных объектов, так как нельзя предусмотреть заранее перечень таких свойств.

В языке С++ имеется так называемая «чистая» разновидность полиморфизма, которая снимает указанные ограничения. Суть её в следующем: связывание сигнатуры функции и требуемого на данный момент исполняемого кода функции осуществляется на этапе выполнения программы. Такой механизм носит название позднего (или динамического) связывания. Реализуется данный механизм

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