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

Рассмотрим механизм на примере двух потоков. В каждом из таких потоков работают по одному “циклу выборки сообщений”. Код таких циклов на С++ приведен ниже:

While (GetMessage(…)) {

TranslateMessage(…);

DispatchMessage(…); // определяем, кому и как передать сообщение

}

Функция GetMessage в качестве параметров передает структуру, в которой хранится идентификатор сообщения, его название, дата и время, а так же другие данные. Метод DispatchMessage направляет сообщение тому окну, которое активно и над кем происходит событие.

Каждый запущенный поток, по мимо цикла, имеет в распоряжении собственную очередь сообщений. В такую очередь сообщения сыпаться из общей очереди, называемой – “сырая” (RIT, Raw Input Thread).

Механизм функционирует следующим образом: пользователь вызывает события, например, щелчок левой клавиши мыши. Задачи системы, отреагировать на это, и выработать управляющее воздействие. Событие обрабатывает ОС Windows. Далее, она помещает ее в очередь RIT, откуда сообщение рассылается в очереди всех активных потоков. Цикл выборки сообщений считывает его из очереди, и определяет адресата. Если это компонент формы Кнопка, то просмотреть, определен ли для него обработчик в программе. Если он переопределен, то передать ему управление (событие OnMouseClick). Замечание: цикл выборки сообщений работает, пока метод GetMessage возвращает true. В случае получения системного сообщения WM_CLOSE цикл завершается, связанный с ним поток – тоже.

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

ООП – это методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования. В ООП выделяют три части: 1) базовые элементы являются объекты, а не алгоритмы; 2) каждый объект является экземпляром какого-либо определенного класса; 3) классы организованны иерархически. Программа будет ОО только при соблюдении всех трех условий.

Концептуальная база для ООП является – объектная модель. Она имеет 4 основных элемента: абстрагирование, инкапсуляция, модульность и иерархия. Кроме главных, имеются еще три дополнительных (не обязательных, но полезных в объектной модели): типизация, параллелизм и сохраняемость.

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

Существует целый спектр абстракций, начиная с объектов, которые почти точно соответствуют реалиям предметной области, и кончая объектами, не имеющими право на существование:

Абстракция сущности – объект представляет собой полезную модель, некой сущности в предметной области.

Абстракция поведения – объект состоит из обобщенного множества операций.

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

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

Модульность – это свойство системы, которая была разложена на внутренне связанные, но слабо связанные между собой модули. В ООП, под модульностью, понимается физическое разделение классов в группы, составляющие логические структуры проекта.

Иерархия – это упорядочение абстракций, расположение их по уровням. Принято делить иерархию на два способа: структура классов (иерархия «is-a») и структура объектов (иерархия «part of»). Рассмотрим каждый вариант.

Структура классов «is-a» есть наследование. Наследование означает такое отношение между классами (между родителем и потомком), когда один класс заимствует структурную и функциональную часть одного или нескольких других классов. Заимствование у одного класса есть одиночное наследование, у совокупности – множественное. Пример: картина «животного мира», выделение царств, классов и подклассов.

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

Типизация – это способ защититься от использования объектов одного класса вместо другого, или по крайне мере управлять таким использованием. Выделяют сильную, слабую типизацию и ее отсутствие.

Сильная типизация предполагает явно указывать допустимые операции над объектом, и декларирование механизмов взаимодействия с другими объектами. Слабая типизация предполагает частичное объявление списка допустимых операций, механизмов, и существование других способов взаимодействия с другими объектами. Как правило, большая часть таких механизмов представляется «по умолчанию». В некоторых ОО языках (например, Smalltalk) типов нет вообще. Во время исполнения любое сообщение может быть послано любому объекту, и если класс объекта не понимает сообщение, то генерируется сообщение об ошибке.

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

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

Полиморфизм – это механизм ОО подхода, в котором предполагается наличие в некоторый момент времени объекта, обозначающего (относящегося) к одному из классов. И у этих классов есть общий суперкласс и они, хотя и по разному, могут реагировать на одно и то же сообщение, одинаково понимая его смысл. Суть полиморфизма можно продемонстрировать на примере: суперкласс “Птица” (абстрактный). Для него определен чисто виртуальный метод “Летать”. От данного класса наследуют классы “Пингвин”, “Ласточка” и “Курица” – все потомки птицы, но каждый из таких классов по своему определит способ поведения “Летать”. Например, курица начнет бежать с бешенной скоростью, пингвин переваливаясь с одной ноги на другую, дойдет до края берега и в воду, а вот ласточка – полетит, оторвавшись от земли в небо.

13. Понятие объекта и класса. Свойства объекта: состояние, поведение, идентичность. Свойства класса: структура и поведение. Описание классов в языке C++. Объект, класс и инкапсуляция как связанные понятия.

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

Объект обладает состоянием, поведением и идентичностью; структура и поведение схожих объектов определяет общий для них класс. Термины «экземпляр класса» и «объект» взаимозаменяемые.

Термин Класс. В общепонятных терминах, класс это группа, множество или вид с общими свойствами или общим свойством, разновидностями, отличиями по качеству, возможностями или условиями. Если объект обозначает конкретную сущность, определенную во времени и пространстве, то класс определяет абстракцию существенного в объекте. В контексте ООП, класс – это некое множество объектов, имеющих общую структуру и общее поведение.

Различие терминов класс и объект: это отдельные, но тесно связанные понятия. В частности, каждый объект является экземпляром какого-либо класса; класс может порождать любое число объектов. В большинстве практических случаев классы статичны, то есть все их особенности и содержание определенны в процессе компиляции программы. Из этого следует, что любой созданный объект относится к строго фиксированному классу. Сами объекты, напротив, в процессе выполнения программы создаются и уничтожаются. Например, существует класс «млекопитающие», а указание на конкретного представителя («Мой кот, Мурзик») – это объект или экземпляр класса «млекопитающих».

Свойства объекта: состояние есть перечень (обычно статические) всех свойств конкретного объекта и текущими (обычно динамические) значениями каждого из этих свойств. Например, статическое свойство Лифта есть декларированная способность ездить вверх-вниз, а не горизонтально. И значение свойства есть состояние (едет, стоит). Если едет, то откуда и куда. Если стоит, то номер этажа.

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

Свойства объекта: идентичность – это такое свойство объекта, которое отличает его от всех других объектов.

Свойства класса: структура. Выделяют две составляющие класса – интерфейс (облик) и реализация (внутренне устройство). Главное в интерфейсе – объявление операций, востребованных другими классами. Сюда же относят объявление других классов, переменных, констант и исключительных ситуаций, уточняющих абстракцию. Реализация, напротив – ни кому из вне класса не интересна, а значит – она направлена на определении операций, объявленных в интерфейсе. Интерфейс класса принято делить на три части:

Открытая (public) – видимая всем клиентам. Защищенная (protected) – видимую самому классу, его подклассам и друзьям. Закрытая (private) – видимую т. самому классу и его друзьям.

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

Описание классов в языке С++. В стандарте ANSI/ISO языка С++ класс объявляется с помощью ключевого слова class. Синтаксис объявления класса похож на синтаксис объявления структуры.

Объявление Пример
class имя_класса { // закрытые члены класса public: // открытые члены класса } список_объектов; class myClass { int a; public: void set_a(int num); int get_a();};

В объявление класса список_объектов не обязателен.

Объект, класс и инкапсуляция как связанные понятия: Объект является экземпляром класса. Класс определяет поведение объекта, по средством задания его интерфейса и реализации. Интерфейс отражает внешнее поведение объекта, описывая его абстракцию. А внутренняя реализация описывает представление этой абстракции и механизмы достижения желаемого поведения. Внутренняя реализация скрывает от других объектов все детали, не имеющие отношения к процессу взаимодействия объектов (т.е. это инкапсуляция).

14. Создание объектов в языке C++. Распределение объектов в динамической памяти (куче) и автоматическое распределение памяти под объекты (на стеке). Сильные и слабые стороны.

В С++ распределение памяти для переменных осуществляется в стеке и в куче. Когда переменные распределяются в стеке, то при распределении вызывается их конструктор и при выходе их из области видимости вызывается их деструктор. Наибольшее распространение для объекта получил способ выхода из области видимости ч/з возврат функции. Например:

void main {

MyClass Object; // MyClass

SomeFunction(Object); // вызываем функцию SomeFunction с

// параметром типа MyClass

Return 0;}

В данном тексте сначала объект создается в стеке при объявлении (вызов конструктора), затем используется конструктор копирования для передачи параметра в функцию (в области видимости функции появляется экземпляр объекта типа MyClass), а затем переменная Object уничтожается (выход за пределы видимости – процедуры Main). Кроме того, деструктор вызывается в SomeFunction после возврата функции (оператора return).

Замечание: нет необходимости указывать ключевое слово auto перед объявлением имени переменной. Компилятор в его отсутствие автоматически определяет, что распределение идет на стеке. Пример аналогичных конструкций: 1) auto int a; 2) int a;

Второй способ – создание объекта в куче с использованием оператора new:

myClass *pObject = new MyClass;

someFunction(*pObject);

delete pObject;

Использование оператора new приводит к вызову конструктора класса myClass. Для передачи параметра в функцию надо разадресовать указатель pObject. Далее опять происходит вызов конструктора копирования и деструктора в теле функции. Delete pObject освобождает память. Если же определить функцию someFunction(myClass *c), в которую передается указатель на объект, то при ее использовании конструктор копии не будет вызван, т.к. new объект не создается. Поэтому и при возвращении управления функцией не вызывается деструктор.

Использование оператора new подразумевает необходимость освобождения памяти с помощью delete. Иначе происходит утечка памяти – область памяти, использованная для данного объекта, становится недоступной до окончания программы. Часто такие ошибки приводят к замедлению или даже завершению программы, они малозаметны. В случае обработки исключений необходимо дублировать операторы delete для всех используемых указателей, что не является идеальным решением. Кроме того, копирование указателей (Н-Р, при передаче их в функцию) усложняет управление памятью – может случайно удалить уже удаленный указатель. Для того, чтобы избежать таких ошибок, рекомендуется удаленные указатели устанавливать в NULL. Работа с указателями часто бывает небезопасна: передача указателя подразумевает возможность изменения объекта, что может быть нежелательно.

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

15. Методы (члены-функции) в классах языка C++. Специальные методы: статические (static) методы, виртуальные методы и неизменные (immutable) методы в C++.

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

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

Функции-члены можно объявлять статическими (static), но обычно это не делается. Доступ к объявленной статической функции — члену класса возможен только для других статических членов этого класса. У статической функции-члена нет указателя this. Статические функции-члены не могут быть виртуальными. Статические функции-члены не могут объявляться с идентификаторами const (постоянный) и volatile (переменный).

Виртуальная функция(virtual function) является членом класса. Она объявляется внутри базового класса с ключевым словом virtual и переопределяется в производном классе. Если класс, содержащий виртуальную функцию, наследуется, то в производном классе виртуальная функция переопределяется. По существу, виртуальная функция реализует идею "один интерфейс, множество методов", которая лежит в основе полиморфизма. Виртуальная функция внутри базового класса определяет вид интерфейса этой функции. Каждое переопределение виртуальной функции в производном классе определяет ее реализацию, связанную со спецификой производного класса.

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

Функции – члены класса могут объявляться постоянными(с идентифика­тором const). Если функция объявлена постоянной, она не может изменить вызывающий ее объект. Кроме этого, постоянный объект не может вызвать непостоянную функцию-член. Тем не менее, постоянная функция-член мо­жет вызываться как постоянными, так и непостоянными объектами. Пример:

class X {

int some_var; public:

int f1 () const; // постоянная функция-член };

Ключевое слово const указывают следом за списком параметров функции, а не перед именем функции. Возможна ситуация, когда понадобится, чтобы функция-член, оставаясь постоянной, тем не менее была способна изменить один или несколько членов класса. Это достигается заданием модифицируемых членов класса (ключевое слово mutable). Модифицируемый член класса можно изменить с помощью постоянной функции-члена.

16. Конструкторы и деструкторы в языке C++. Соглашения относительно специальных функций-членов класса. Порядок вызова при наследовании. Виртуальные деструкторы.

Конструктор – операция создания объекта и /или его инициализация. Вызывается всякий раз при создании объекта класса. Т.о., любая необходимая объекту инициализация при наличии конструктора выполняется автоматически. Конструктор может быть реализован программистом. Если программист не задал свой конструктор класса, создаётся конструктор по умолчанию. Допустимо создание нескольких конструкторов (перегрузка).

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

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

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

Локальные объекты удаляются тогда, когда они выходят из области видимости. Глобальные объекты удаляются при завершении программы.

Конструктор и деструктор объявляются как члены-функции класса. Имя конструктора совпадает с именем класса. Имя деструктора начинается с символа ~ (тильда), за которым следует имя класса. Замечание: применяется для языка С++.

Адреса (указатель) конструктора и деструктора получить невозможно.

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

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

В отличие от конструктора деструктор может быть виртуальным. Если он не объявлен виртуальным можно столкнуться с ситуацией неправильной ликвидацией объекта. Деструктор объявляется с ключевым словом virtual – при уничтожении объекта, будет автоматически определен его деструктор, исходя из его типа. Ключевое слово virtual позволяет указать на то, что вызовы деструкторов будут компоноваться уже во время выполнения программы.

17. Понятие модуля. Модуль как средство борьбы со сложностью программной системы. Принципы проектирования модулей. Модули в языке C++. Модульность и инкапсуляция как связанные понятия.

Приступая к разработке каждой программы, следует иметь ввиду, что она, как правило, является большой системой, поэтому нужно принять меры для ее упрощения. Для этого программу разрабатывают по частям, которые называются программными модулями. Программный модуль - это любой фрагмент описания процесса, оформляемый как самостоятельный программный продукт, пригодный для использования в описаниях процесса. Это означает, что каждый программный модуль программируется, компилируется и отлаживается отдельно от других модулей программы, и тем самым, физически разделен с другими модулями программы. Более того, каждый разработанный программный модуль может включаться в состав разных программ, если выполнены условия его использования, декларированные в документации по этому модулю. Т.о., программный модуль может рассматриваться и как средство борьбы со сложностью программ, и как средство борьбы с дублированием в программировании.

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

Модульность – это свойство системы, которая была разложена на внутренне связные, но слабо связанные между собой модули.

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

Модули в языке C++ позволяют программисту оформлять собственные функции, константы и типы данных в независимые программные единицы, которые могут быть использованы как в программах, так и в других модулях. Желательно, чтобы набор функций, констант и типов данных в модуле был логически связан. Например, функции и типы данных для работы с комплексными числами следует размещать одном модуле, а функции и типы данных с матрицами в другом.

При создании модуля используются два типа файлов: с телом модуля (*.c) и заголовочный (*.h). Имена файлов относительно одного модуля должны совпадать.

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

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

// complex.h - Пример реализации заголовочного файла модуля.

// Модуль содержит тип данных комплексного числа,

// а также функцию вывода комплексного числа на экран.

typedef struct {

double Re, Im;

} complex;

extern void OutComplex(complex);

Для использования модуля в программе достаточно включить в текст программы директиву перепроцессора #include с указанием соответствующего заголовочного файла. В отличие от стандартных модулей собственные модули указываются в двойных кавычках.

18. Типизация (декларация) данных в языках программирования. Задачи типизации. Слабая и сильная типизация. Достоинства и недостатки каждой. Типизация в языке C++. Встроенные типы данных. Определение new типов данных.

Существует два полюса типизации – слабая и сильная. Каждый язык программирования сочетает в себе черты обоих типов, больше склоняясь к одному из них. Примеры языков со слабой типизацией – SmallTalk, JavaScript и VBScript, а с сильной - C++ и Object Pascal. Слабая типизация позволяет не типизировать (декларировать) объекты-переменные, на этапе компиляции не происходит проверка правильности использования данной переменной. Такой подход позволяет реализовывать позднее связывание, т.е. тип объекта определяется только на стадии выполнения. Т.о., обеспечивается гибкость языка, но за счет снижения производительности (компилятор/интерпретатор вынужден хранить информацию о переменных, чтобы правильно обработать, а также не может оптимизировать код программы, т.к. заранее типы данных неизвестны). Сильная типизация предусматривает более жесткие ограничения – каждая переменная д.б. объявлена, что делает программу менее гибкой, однако многие проблемы несовместимости типов решаются уже на стадии компиляции. В языке C++ сильное связывание несколько смягчено возможностями преобразования типов, в том числе и динамического.

(+ сильной типизации): 1). Каждая переменная д.б. изначально декларирована с тем, чтобы быть приписанной к определенному типу, и должна использоваться, теми способами, которые этому типу соответствуют. 2). Переменные могут быть сгруппированы в объекты с хорошо определенной структурой и процедурами для манипуляции ими. Объект одного типа не может быть использован там, где ожидается использование объекта другого типа. 3). Компиляторы используют информацию о типах для обнаружения определенных видов ошибок, таких как попытка задействовать величину с плавающей точкой как указатель. 4). Типизация повышает эффективность исполнения, позволяя компилятору заранее оптимизировать машинный код.

(– сильной типизации): 1). Сильно типизированная природа языков программирования систем не способствует повторному использованию кода, а заставляет программистов создавать массу однотипных интерфейсов, каждый из которых рассчитан на объекты специфических типов. 2). Большой объем кода программ. 3). Раннее связывание;

(+ сильной типизации): позднее связывание.

(– сильной типизации): понижается надежность и производительность программ.

19. Соглашения по преобразованию (приведению) типов данных в языке C++. Операторы const_cast, reinterpret_cast, static_cast, dynamic_cast.

С помощью механизма приведения типов можно явно преобразовывать данные одного типа в данные другого. Есть два равнозначных способа:

(имяТипа) значение // была определена еще в языке С

имяТипа (значение) // введена в С++ для сходства с вызовом функции

Оператор const_cast() используется для добавления или удаления из типа модификаторов const (и volatile). Другими словами – для устранения постоянства объекта. Например, если член-функция класса объявлена как const, она не может изменять данные в этом классе. Однако, с помощью оператора const_cast можно преобразовать ее к объекту, отличному от const и имеющему тот же тип. Это позволит член-функции const изменить данные класса:

Void f() const // член-функция класса myClass

{ Somevalue++; // запрещено

Const_cast<myClass*>(this)->SomeValue++; // можно!!}

Данный пример преобразовывает объект класса myClass в объект такого же типа, но без модификатора const в методе f().

Оператор reinterpret_cast() выполняет преобразование между указателями, их типами и числами в указатели и наоборот. Синтаксис: reinterpret_cast<Type>(Object);

Type может быть указателем, ссылкой, арифметическим типом, указателем на функцию или на число. Если существует неявное преобразование из Object в Type, то оператор работает, иначе – генерирует ошибку времени компиляции. Результат является системозависимым (то есть зависит от реализации). Пример – преобразование указателя к целому типу и наоборот:

int func(void* p)

{ Return reinterpret_cast<int>(p) }

int main()

{ int value=8;

int j=Func(reinterpret_cast<void*>(value));// 8

int j=Func(reinterpret_cast<void*>(&value)); // вернет непредсказуемый адрес (н-р, 1234567)

}

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

Синтаксис: dynamic_cast <Type> (Object)

Аргумент Type – это тип, к которому выполняется приведение. Аргумент Object – преобразуемый объект. Type должен быть указателем или ссылкой на тип определенного класса. Аргумент Object должен быть выражением для указателя или ссылки. В случае успеха dynamic_cast преобразует Object к желаемому типу. Поскольку в случае неудачного приведения ссылки возбуждается исключение bad_cast, то применение данного оператора безопаснее традиционного приведения, которое терпит неудачу и не указывает на ошибку. Использование оператора dynamic_cast также называется безопасным приведением типа. Преобразование указателя или ссылки порожденного класса в указатель или ссылку базового класса выполняется во время компиляции, а результат направляется в указатель или ссылку на подобъект базового класса. Такое преобразование называется преобразованием вверх (upcast). По аналогии обратное преобразование – преобразование вниз (downcast).

В отличие от dynamic_cast(), оператор static_cast() используется в контексте типа объекта времени компиляции (т.е. компилятору заранее известно, какой тип объекта будет преобразовываться).

Синтаксис аналогичен dynamic_cast(). И Type, и Object должны быть известны на стадии компиляции. Если существует определенный в C++ метод, обеспечивающий запрашиваемый перевод, то он будет использован (например, из float в double).

20. Специальный полиморфизм в языке C++. Перегрузка функций и операторов.

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

Функции С++, которые не являются частью протокола описания класса, могут быть перегружены. Такие функции должны иметь различные списки параметров. Различаться должны количество параметров или их типы (или и то, и другое). При этом переменные со статусом const отличаются от прочих (т.е void f(char * c) и void f(const char * c) различаются). Возможность перегрузки определяется только сигнатурой функции (набором параметров), а не возвращаемым результатом.

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

возвращаемый тип имя класса :: operator знак операции (список аргументов)

{ тело функции }

Возвращаемый тип может быть любым. Знак операции – знак перегружаемой операции. Список аргументов зависит от реализуемой операции. Ограничения:

· во время перегрузки операторов нельзя менять приоритет операций.

· во время перегрузки операторов нельзя менять число операндов.

· нельзя перегрузить операции: . :: ?

· нельзя перегружать операторы препроцессора.

Перегрузка бинарных операций:

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

MyClass MyClass::operator + (MyClass * Src)

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

21. Параметрический полиморфизм. Параметризованные классы (template) и функции в языке C++ как средство статического полиморфизма.

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

Статический полиморфизм – выражается в перегрузке операторов, функций.

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

Чтобы использовать шаблонные классы программисту достаточно один раз описать шаблон класса. Каждый раз, когда требуется реализация класса для нового типа данных, программист, использует простую краткую запись, сообщает об этом компилятору, который и создает исходный код для требуемого шаблонного класса.

Шаблоны:template <class имя переменной>

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

Родовая (шаблонная, параметризованная) функция определяет базовый набор операций, который можно применить к разным типам данных.

template <class T> <возвращаемый тип> <имя функции> (список входных параметров)

где class T – имя типа, для которого будет создаваться функция. Пример:

Template <class T> void sort (T a[], int n) {}

// sort - функция сортировки, параметры которой будут определены позже,

// н-р, sort <int> (a, 10); // если необходимо отсортировать массив целых чисел

// или sort <student>(a, 10); // сортировка массива студентов из 10 человек

Основные «свойства» шаблонов:

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

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

- Все описания шаблонов функций начинаются с ключевого слова template, за которым следует список формальных параметров шаблона, заключаемый в угловые скобки (< и >); каждому формальному параметру должно предшествовать ключевое слово class. Ключевое слово class, используемое при определении типов параметров шаблона функции означает: «любой встроенный тип или тип, определяемый пользователем». Формальные параметры в описании шаблона используются для определения типов параметров функции, типа возвращаемого функцией.

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

- Описание шаблона класса выглядит как традиционное описание класса, за исключением заголовка template <class T>, указывающего на то, что это описание является шаблоном класса с параметром типа Т, обозначающим тип классов, которые будут создаваться на основе этого шаблона.

- Каждое описания функции-элемента вне заголовка шаблона класса начинается с заголовка template <class T>, за которым следует описание функции, подобное стандартному описанию, но как тип элемента класса всегда указывается параметр типа Т.

22. Полная и частичная специализация шаблонов в языке C++.

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

Рассмотрим специализацию шаблонов на примере:

template <class T> void sort (T a[], int n) {}

Процедура должна иметь то же имя, что и шаблон:

void sort (student a[], int n) {}

Два варианта вызова функции sort:

1) sort <int> (a[], 10); // Компилятор не видит явного определения функции для данного выражения, в связи с чем запускает первую функцию, которая работает с любым типом

2) sort <student> (a[], 10); // компилятор найдет явный метод обработки данного выражения, им станет вторая функция, объявленная ранее в шаблоне.

Полная специализация шаблона подразумевает задание всех параметров.

Частичная специализация шаблона класса - это шаблон, и ее определение похоже на определение шаблона. Оно начинается с ключевого слова template, за которым следует список параметров, заключенный в угловые скобки. Список параметров отличается от соответствующего списка параметров общего шаблона, т.е. в списке представлены параметры, для которых фактические аргументы еще неизвестны.

Имя частичной специализации совпадает с именем того общего шаблона, которому она соответствует. Однако за ее именем всегда следует список аргументов. Частичная специализация шаблона класса неявно конкретизируется при использовании в программе. Если для шаблона класса объявлены частичные специализации, компилятор выбирает то определение, которое является наиболее специализированным для заданных аргументов. Если же ни одно из них не подходит, используется общее определение шаблона. Рассмотрим примеры:

Пусть заданы три разных шаблона:

Шаблон №1: template <class A, class B> class SomeClass {};

Провели параметризацию класса конкретными типами: SomeClass <int, char> a;

Шаблон №2: template < > class SomeClass <int, char> {};

Шаблон №3: template <class A> class SomeClass <A, char> {};

Если:

SomeClass <int, char> a; // то создается 2 шаблон

SomeClass <char, char> b; // то создается 3 шаблон

SomeClass <float, short> c; // то создается 1 шаблон

Т.е. сначала ищется полное совпадение, затем частичное.

23. Простое и множественное наследование в языке C++. Полиморфное поведение объектов при наследовании. Проблема «среза» в полиморфизме наследования и способы решения.

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

Можно рассматривать объекты базового и производного классов как подобные; их общность выражается в атрибутах и функциях базового класса. Все объекты любых классов, открыто порожденных общим базовым классом, могут рассматриваться как объекты этого базового класса. Открытые элементы базового класса доступны всем функциям программы. Закрытые элементы базового класса доступны только функциям-элементам и друзьям базового класса. Защищенный уровень доступа служит промежуточным уровнем защиты м/у открытым доступом и закрытым доступом. Защищенные элементы базового класса могут быть доступны только элементам и друзьям базового класса и элементам и друзьям производного класса. Элементы производного класса могут ссылаться на открытые и защищенные элементы базового класса простым использованием имен этих элементов. Защищенные данные «взламывают» инкапсуляцию: изменение защищенных элементов базового класса может потребовать модификации всех производных классов.

Полиморфизм реализуется посредством виртуальных функций. Если класс, содержащий виртуальную функцию, наследуется, то в производном классе виртуальная функция переопределяется. Виртуальная функция внутри базового класса определяет вид интерфейса этой функции. Каждое переопределение виртуальной функции в производном классе определяет ее реализацию, связанную со спецификой производного класса. Т.о., переопределение создает конкретный метод. При переопределении виртуальной функции в производном классе, ключевое слово virtual не требуется. Виртуальная функция может вызываться так же, как и любая другая функция-член класса. Наиболее интересен вызов виртуальной функции через указатель (поддерживается динамический полиморфизм). Если указатель базового класса ссылается на объект производного, который содержит виртуальную функцию и для которого функция вызывается через указатель, то компилятор определяет какую версию виртуальной функции вызвать, основываясь на типе объекта, на который ссылается указатель. При этом определение конкретной версии виртуальной функции имеет место не в процессе компиляции, а в процессе выполнения программы. Т.е. тип объекта, на который ссылается указатель, и определяет ту версию виртуальной функции, которая будет выполняться. Поэтому, если два и более различных класса являются производными от базового, содержащего виртуальную функцию, то, если указатель базового объекта ссылается на разные объекты этих производных классов, выполняются различные версии виртуальных функций.

Проблема «среза» в полиморфизме:

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

Пример:

Пусть дан класс родитель – Person.GetName(), и два класса-наследника – Student.GetID() и Prepod.GetAddress().

Person *p = new Student;

p -> GetName(); // OK

p -> GetID(); // Error СРЕЗ

Для решения данной проблемы используются операторы преобразования типа – dynamic_cast и static_cast. Воспользуемся вторым оператором, т.к. в классе предке нет виртуальной функции:

static_cast<student> (p) -> GetID(); // OK

или же (student *) (p) -> GetID(); // OK

24. Абстрактные классы и интерфейсы. Описание на языке C++. Реализация интерфейсов.

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

Class CShape

{ public:

virtual void View(void) = 0; // виртуальная функция

}

Class CTriangle : public CShape

{ public:

void View(void); // переопределение виртуальной функции

}

Единственным назначением абстрактного класса является создание соответствующего базового класса, от которого другие классы могут унаследовать интерфейс и реализацию. Классы, объекты которых могут быть реализованы, называются конкретными классами.

Интерфейс (рассматривается в разрезе языка С++) – это класс, без данных, и только с чисто виртуальными методами. Т.е. Интерфейс – это абстрактный класс. Одним из методов реализации интерфейсов является наследование, с реализацией методов содержащихся в интерфейсе. Примеры:

Class Iperson { virtual void Get_fname(void) = 0 }; // пример интерфейса

Class Istudent { virtual void Get_ID(void) = 0 }; // еще один пример интерфейса

Class <Name class> : public person, public student // пример множественного наследования

{ void Get_fname(void);

void Get_ID(void);}

Интерфейс определяет набор функций и процедур, которые могут быть использованы для взаимодействия программы с объектом. Определение конкретного интерфейса известно как разработчику, так и пользователю интерфейса, и воспринимается как соглашение о правилах объявления и использования этого интерфейса. В классе может быть реализовано несколько интерфейсов. В результате объект становится «многоликим», являя клиенту каждого интерфейса своё особое лицо. Интерфейсы представляют собой только определения того, каким образом могут сообщаться м/у собой объект и его клиент.

Наши рекомендации