Содержание
- 2. Понятия теории систем и системного анализа (ТСиСА) Система (С.) – совокупность (множество) отдельных объектов и (обязательные)
- 3. Понятия теории систем и системного анализа (ТСиСА) Задачи ТСиСА: Декомпозиция – представление С. в виде подсистем
- 4. Основные принципы системного анализа: Целостность – рассмотрение системы одновременно как единое целое и как подсистему для
- 5. Понятия теории систем и системного анализа (ТСиСА) Классификация систем: Физические – абстрактные. Ф. – реальные С.
- 6. Понятия теории систем и системного анализа (ТСиСА) Упрощенная последовательность практических действий при системном анализе некого объекта
- 7. Понятия объектно-ориентированного программирование (ООП) Имея теперь некоторые базовые представления о системах и системном анализе можно увидеть,
- 8. Понятия объектно-ориентированного программирование (ООП) Три главные свойства объектно-ориентированного языка (ООЯ) программирования: Инкапсуляция, Полиморфизм, Наследование. Инкапсуляция (encapsulation,
- 9. ООП Понятия объектно-ориентированного программирование (ООП) Классы. Класс – это абстрактный тип данных, определяемый программистом, моделирующий реальный
- 10. Инкапсуляция (encapsulation, incapsulation) – это способ формирования объектов в ООП, состоящий в ограничении внешнего доступа к
- 11. Объявление класса: class { [ :] …………………………….. [ :] } [ ]; спецификатор_доступа – это одно
- 12. // Файл Book.h – спецификация класса СВоок #pragma once /*Чтобы компилятор включал объявление типа только один
- 13. Классы Данные и функции класса. Данные класса называются: поля (данные-члены, компонентные данные). Функции класса называются: методы
- 14. Классы и объекты Конкретные переменные типа класс называются экземплярами класса, или объектами. Время жизни и видимость
- 15. Классы и объекты Описание объекта задает его тип (имя класса) и, возможно, необходимые для инициализации членов-данных
- 16. Вложенные классы Классы Один класс можно определить внутри другого ‒ это вложенные классы. Поскольку объявление класса
- 17. Пространства имен предназначены для локализации имен идентификаторов и предотвращения их конфликтов. Среда программирования С++ работает с
- 18. Ключевое слово namespace позволяет разделить глобальное пространство имен на декларативные области (declarative region). Пространство имен ‒
- 19. Директива using Пространство имён Текст программы, в которой часто встречаются ссылки на элементы пространства имен станет
- 20. ООП. Наследование Механизм наследования классов позволяет строить иерархии, в которых производные классы получают элементы родительских, или
- 21. ООП. Наследование #include class building { int rooms; int floors; int area; public: void set_rooms (int
- 22. ООП. Наследование // продолжение int main () { house h; school s; h.set_rooms (12); h.set_floors (3);
- 23. ООП. Наследование Простое (или одиночное) наследование – это наследование, при котором производный класс имеет только одного
- 24. ООП. Наследование Множественное наследование отличается от простого (одиночного) наличием нескольких базовых классов: class А {}; class
- 25. ООП. Наследование Конструкторы при наследовании Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы. Порядок
- 26. ООП. Наследование Деструкторы при наследовании Правила наследования деструкторов Деструкторы не наследуются, и если программист не описал
- 27. Полиморфизм – возможность использовать в различных классах иерархии одно имя для обозначения сходных по смыслу действий
- 28. Перегрузка функций (function overloading) – это использование одного имени для нескольких функций, отличающихся либо другими типами
- 29. Перегрузка функций ПРИМЕР: надо сделать функции поиска в базе данных, каждая функции имеет осмысленное имя: int
- 30. Перегрузка функций Как компилятор решает, какую именно функцию надо использовать для данного вызова (запроса). Компилятор находит
- 31. Перегрузка функций Примеры // Различные типы данных параметров: // выводится на экран значение параметра #include //
- 32. Перегрузка операторов (operator overloading) – это изменение смысла оператора, при котором возможно одновременного существования в одной
- 33. Операторная функция-член имеет следующий вид: :: operator# (список-аргументов) { ... // Операции } Обычно операторная функция
- 34. Программа создает класс lос, в котором хранятся географические координаты: широта и долгота. В ней перегружается оператор
- 35. Ограничения на перегруженные операторы Во-первых, нельзя изменить приоритет оператора. Во-вторых, невозможно изменить количество операндов оператора. (Однако
- 36. Шаблоны в С++ ООП С помощью шаблонов (templates) можно создавать родовые (обобщённые) функции (generic functions) и
- 37. ООП Обобщенная функция (шаблон функции – template function) определяет универсальную совокупность операций, применимых к различным типам
- 38. ООП Обобщенная функция, меняющая местами две переменные. Поскольку процесс перестановки не зависит от типа переменных, его
- 39. ООП Уточним понятия связанные с шаблонами. Обобщенная функция (т.е. функция, объявленная с помощью ключевого слова template)
- 40. ООП Функция с двумя обобщенными типами Шаблоны функций Используя список, элементы которого разделены запятыми, можно определить
- 41. ООП Ограничения на обобщенные функции Шаблоны функций Обобщенные функции напоминают перегруженные, но на них налагаются еще
- 42. ООП Шаблоны функций Применение обобщенных функций. Обобщенная сортировка #include using namespace std; template void bubble (
- 43. ООП Шаблоны функций Применение обобщенных функций. Обобщенная сортировка int main() { int iarray[7] = {7, 5,
- 44. ООП Шаблоны функций Применение обобщенных функций. Обобщенная сортировка Программа выводит на экран следующие результаты: Неупорядоченный массив
- 45. ООП При наследовании часто бывает необходимо, чтобы поведение некоторых методов базового класса и классов-наследников различалось. Можно
- 46. ООП Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию-метод виртуальной; в С++ это
- 47. ООП Таким образом, объявление метода f() в базовом и производном классах должно быть таким: virtual int
- 48. ООП С перегрузкой функций "разбирается" компилятор, правильно подбирая вариант функции в той или иной ситуации. Полиморфизм
- 49. ООП Объясним виртуальные функции ещё раз по-другому: Работа с объектами чаще всего производится через указатели. Указателю
- 50. ООП Правила описания и использования виртуальных методов: □ Если в базовом классе метод определен как виртуальный,
- 51. ООП Сравнение раннего и позднего связывания Раннее связывание (early binding) означает события, происходящие на этапе компиляции.
- 52. ООП Абстрактные классы Класс, содержащий хотя бы один чисто виртуальный метод, называется абстрактным (abstract class). Абстрактные
- 53. ООП Практическое занятие: Вызов виртуальной функции с помощью указателя на объект базового класса Виртуальные функции #include
- 54. ООП Виртуальные функции Указателю р присваивается адрес объекта b, а функция vfunc() вызывается с помощью указателя
- 55. ООП Виртуальные функции На первый взгляд, переопределение виртуальной функции в производном классе мало отличается от обычной
- 56. ООП В языке С++ для поддержки объектно-ориентированного программи-рования используется динамическая идентификация типа (RTTI ‒ Run-Time Type
- 57. ООП Для идентификации типа объекта используется оператор typeid, определенный в заголовке #include . Его формат: typeid
- 58. ООП #include #include using namespace std; class Mammal {public: //класс Mammal – полиморфный virtual bool lays_eggs()
- 59. ООП Динамическая идентификация типа Если оператор typeid применяется к указателю на объект полиморфного базового класса, тип
- 60. ООП #include using namespace std; class Mammal { public: virtual bool lays_eggs() {return false;} // ...
- 61. ООП #include using namespace std; template class myclass { T a; public: myclass(T i) { a
- 62. ООП В языке С++ существуют пять операторов приведения типов. Первый оператор является вполне традиционным и унаследован
- 63. ООП Оператор dynamic_cast Операторы приведения типа Оператор dynamic_cast осуществляет динамическое приведение типа с последующей проверкой корректности
- 64. ООП Оператор dynamic_cast Операторы приведения типа Пример. Класс Base является полиморфным, a Derived – производным от
- 65. ООП Применение dynamic_cast к шаблонным классам Операторы приведения типа #include using namespace std; template class Num
- 66. ООП Оператор const_cast Операторы приведения типа Оператор const_cast используется для явного замещения модификаторов const и/или volatile.
- 67. ООП Оператор static_cast Операторы приведения типа Оператор static_cast выполняет неполиморфное приведение. Его можно применять для любого
- 69. Скачать презентацию
Понятия теории систем
и системного анализа (ТСиСА)
Система (С.) – совокупность
Понятия теории систем
и системного анализа (ТСиСА)
Система (С.) – совокупность
Теория систем (Общая теория систем) – это наука, предметом которой является разработка логико-концептуального и математического аппарата теоретического исследования объектов, представляющих собой системы.
Целью исследований в рамках этой теории является изучение:
различных видов и типов С.;
основных принципов и закономерностей поведения С.;
функционирования и развития С.
Системный анализ - совокупность понятий, методов, процедур и технологий для изучения, описания, реализации явлений и процессов различной природы и характера, междисциплинарных проблем; это совокупность общих законов, методов, приемов инженерного исследования таких систем.
Цель системного анализа:
полнее понять механизмы функционирования С.;
повысить эффективность функционирования С.;
получить систематические, планомерно-поэтапные методы (способы) анализа и создания (построения) С.
Системный подход – это подход, при котором любая система (объект) рассматривается как совокупность взаимосвязанных компонентов, имеющая выход (цель), вход (ресурсы), связь с внешней средой, обратную связь.
Любую предметную область можно рассматривать как систему.
ООП
И+ПРГ
Понятия теории систем
и системного анализа (ТСиСА)
Задачи ТСиСА:
Декомпозиция –
Понятия теории систем
и системного анализа (ТСиСА)
Задачи ТСиСА:
Декомпозиция –
Анализ – определение свойств С. и среды её окружающей.
Синтез – построение С., обладающей заданными свойствами.
Оптимизация – нахождение С. с экстремальными значениями её функционалов.
Некоторые термины:
Система – это некоторая совокупность взаимосвязанных объектов (компонентов С.), обладающая свойствами, не сводящимися к свойствам отдельных объектов.
Подсистема – это часть С. с некоторыми связями и отношениями, любая С. состоит из подсистем, подсистема любой С. может быть сама рассмотрена как С. (иерархическая вложенность подсистем).
Надсистема – это более крупная с С., частью которой является рассматриваемая С.
Элемент – компонент нижнего иерархического уровня С. (не имеющий внутренней структуры).
Границы системы – это материальные и нематериальные ограничители, дистанцирующие систему от окружающей среды.
Место С. – место в иерархии, определяет надсистему, в которую входит данная система, системы одного уровня с которыми взаимодействует С., подсистемы входящие в данную С.
Рамки С. – ресурсные, технологические (функциональные), географические и другие границы данной С.
Структура – устойчивая картина взаимоотношений между компонентами С. (картина связей и их стабильностей).
Процесс – динамическое изменение С. во времени.
Функция – процесс, происходящий внутри С. и имеющий определённый результат.
Состояние – положение С. относительно других её положений.
Поведение – целеориентированная активность С., служащая для осуществления контакта с окружающей средой.
ООП
И+ПРГ
Основные принципы системного анализа:
Целостность – рассмотрение системы одновременно как единое
Основные принципы системного анализа:
Целостность – рассмотрение системы одновременно как единое
Иерархичность строения – наличие множества компонентов, расположенных на основе подчинения компонентов низшего уровня компонентам высшего уровня.
Структуризация – проведение анализа компонентов системы и их взаимосвязи в рамках конкретной организационной структуры. Как правило, процесс функционирования системы обусловлен не столько свойствами ее отдельных компонентов, сколько свойствами самой структуры (связями между компонентами).
Множественность – использование множества математических, кибернетических и экономических моделей для описания отдельных компонентов и системы в целом.
Понятия теории систем
и системного анализа (ТСиСА)
ООП
И+ПРГ
Понятия теории систем
и системного анализа (ТСиСА)
Классификация систем:
Физические – абстрактные.
Понятия теории систем
и системного анализа (ТСиСА)
Классификация систем:
Физические – абстрактные.
Простые – сложные. Простые – Большие – Сложные: Простые – мало элементов, немного однородных связей; Большие – много элементов, много однородных связей, Сложные – много элементов, много неоднородных связей).
Сложные С. – структурная и функциональная сложность.
Отличительные свойства сложных С.: (а) робастность, (б) неоднородные связи: структурные (в т.ч. иерархические), функциональные, отношения истинности, каузальные (причинно-следственные) и казуальные (случайные);
Динамические – статические. С. имеют изменяющиеся во времени процессы или нет
Открытые-замкнутые. По отношению системы к окружающей среде
Естественные-искусственные-виртуальные-смешанные.
С управлением – неуправляемые.
Непрерывные – дискретные.
Детерминированные – стохастические.
и др.
ООП
И+ПРГ
Понятия теории систем
и системного анализа (ТСиСА)
Упрощенная последовательность практических действий
Понятия теории систем
и системного анализа (ТСиСА)
Упрощенная последовательность практических действий
1. Определение задач исследования (анализа) или синтеза С.
2. Определение назначения и целей С.
3. Декомпозиция С.: выявление подсистем и элементов.
4. Определение структуры системы (наличия и характеристик связей между компонентами С.)
5. Определение функций системы и её компонентов.
6. Определение задач решаемых С., её подсистемами и элементами.
7. Определение принципов моделирования компонентов С. и С. в целом (в зависимости от задач исследования (анализа) или синтеза).
8. Разработка модели системы и её компонентов (в настоящее время чаще всего программной модели).
9. Исследование модели С. (проведение с ней экспериментов).
10. Анализ результатов исследования модели С.
11-а. Формирование выводов анализа, если исследуется существующая система.
11-б. Синтез (создание) новой системы.
ООП
Понятия объектно-ориентированного программирование (ООП)
Имея теперь некоторые базовые представления о системах
Понятия объектно-ориентированного программирование (ООП)
Имея теперь некоторые базовые представления о системах
Объект в ООП – это реальная сущность, описывающая (моделирующая) некоторый компонент процесса или явления. Он обладает состоянием и поведением, обменивается сообщениями с другими объектами. Это может быть подсистема или элемент С.
Состояние объекта описывается совокупностью параметров и свойств, характерных именно этому объекту. Значения этих параметров и свойств изменяется во времени как у реального объекта.
Поведение объекта определяется совокупностью операций, которые можно выполнять над этим объектом.
Объекты, имеющие сходные (в определенных пределах) наборы свойств и параметров, а также набор операций объединяются в класс однотипных объектов.
__________________________________________________
1 Парадигма – устоявшиеся системы научных взглядов, в рамках которых ведутся исследования – комплекс теорий, стандартов и методов, которые представляют способ организации знаний.
Парадигма программирования — это парадигма, определяющая стиль программирования, иначе говоря – некоторый цельный набор идей и рекомендаций, определяющих стиль написания программ.
Парадигма программирования представляет (и определяет) то, как программист видит выполнение программы. Например, в объектно-ориентированном программировании программист рассматривает программу как набор взаимодействующих объектов, тогда как в функциональном программировании программа представляется в виде цепочки вычисления функций.
ООП
И+ПРГ
Понятия объектно-ориентированного программирование (ООП)
Три главные свойства объектно-ориентированного языка (ООЯ) программирования:
Понятия объектно-ориентированного программирование (ООП)
Три главные свойства объектно-ориентированного языка (ООЯ) программирования:
Инкапсуляция (encapsulation, incapsulation).
Это механизм, связывающий воедино код и данные, которыми код манипулирует, и защищает их от несанкционированного и неправильного использования. В ООЯ код и данные можно "упаковать" в "чёрный ящик" – объект (object). Объект и есть средство инкапсуляции.
Объект представляет собой сложную переменную, тип которой определён программистом.
Внутри объекта код и данные могут быть закрытыми (private) или открытыми (public). Открытая часть объекта доступна извне из любой части программы и обеспечивает управляемое взаимодействие (интерфейс) между объектами.
Полиморфизм (polymorphism).
Это атрибут, позволяющий организовать через один интерфейс доступ к целому классу методов. Выбор конкретного метода определяется компилятором в зависимости от ситуации (например, в типом переданных извне в объект данных).
Наследование (inheritance).
Это процесс, в ходе которого один объект приобретает свойства другого. Тем самым в ООЯ реализуется идея классификации (classification), когда конкретный объект является специфическим экземпляром более общей разновидности.
ООП
И+ПРГ
ООП
Понятия объектно-ориентированного программирование (ООП)
Классы.
Класс – это абстрактный тип данных, определяемый
ООП
Понятия объектно-ориентированного программирование (ООП)
Классы.
Класс – это абстрактный тип данных, определяемый
Данные класса называются полями, синоним – данные-члены класса (по аналогии с полями структуры), а функции методами, синоним – функции-члены. Поля и методы называются элементами класса.
Конкретные переменные типа "класс" называются объектами (экземплярами или членами класса). Например, class myclass.
Конструкторы.
Конструктор – это особая функция, являющаяся членом класса. Её имя всегда совпадает с именем класса. Например, myclass(). Конструктор предназначен для инициализации нужной части объекта и автоматически вызывается программой в момент создания объекта.
Для глобальных и статических локальных объектов конструкторы вызываются лишь однажды. Для локальных объектов конструкторы вызываются каждый раз при входе в соответствующий блок.
Деструкторы.
Деструктор – это особая функция-антипод конструктора. Её имя совпадает с именем класса с тильдой (~) перед ним. Например, ~myclass(). Деструктор предназначен для удаления объекта. Это может потребоваться для освобождения памяти или закрытия открытого ранее файла. Деструктор вызывается автоматически при выходе объекта из области видимости (для локальных объектов - при выходе из блока, где они были объявлены; для глобальных – при выходе из main; для объектов, заданных через указатели – неявно при использовании операции delete).
И+ПРГ
Инкапсуляция (encapsulation, incapsulation) – это способ формирования объектов в ООП, состоящий
Инкапсуляция (encapsulation, incapsulation) – это способ формирования объектов в ООП, состоящий
Этот механизм, связывает воедино код и данные, которыми код манипулирует, и защищает их от несанкционированного и неправильного использования. В ООЯ код и данные можно "упаковать" в "чёрный ящик" – объект (object). Объект и есть средство инкапсуляции.
Объект представляет собой сложную переменную, тип которой определён программистом.
Внутри объекта код и данные могут быть закрытыми (private) или открытыми (public). Открытая часть объекта доступна извне из любой части программы и обеспечивает управляемое взаимодействие (интерфейс) между объектами.
Основным механизмом инкапсуляции является логическая абстракция класс – модель реального объекта, объект, в свою очередь, это конкретный экземпляр класса, его физическая реализация.
Класс – это тип данных, определяемый программистом, в котором объединяются структуры данных и функции их обработки.
Класс содержит константы и переменные, называемые полями, а также операции и функции выполняемые над данными – методы. Доступ к полям класса возможен только через вызов соответствующих методов.
ООП. Инкапсуляция, классы и объекты
И+ПРГ
Объявление класса:
class <имя_класса> {
[<спецификатор_доступа>:]
<данные и функции>
……………………………..
[<спецификатор_доступа>:]
<данные и функции>
} [<список_объектов>];
спецификатор_доступа
Объявление класса:
class <имя_класса> {
[<спецификатор_доступа>:]
<данные и функции>
……………………………..
[<спецификатор_доступа>:]
<данные и функции>
} [<список_объектов>];
спецификатор_доступа
public – открытая секция класса, открывает доступ к функциям и данным этого класса из других частей программы, это интерфейс класса;
private – закрытая секция класса, объявляет функции и данные закрытые от внешнего доступа и открытые только членам этого класса, действует по умолчанию;
protected – защищённая секция класса, этот спецификатор используется только при наследовании классов.
- список_объектов – объявляет объекты класса, можно опускать и объявлять объекты позже.
Классы и объекты
Количество одинаковых спецификаторов_доступане ограничено, порядок их следования – произвольный.
Методы, расположенные в открытой части (public), формируют интерфейс класса и могут вызываться другим (внешним) объектом.
Доступ к закрытой секции класса (private) возможен только из собственных методов класса.
Доступ к защищённой секции класса (protected) -- из собственных методов класса и из методов классов-потомков данного класса (см. наследование).
И+ПРГ
// Файл Book.h – спецификация класса СВоок
#pragma once /*Чтобы компилятор
// Файл Book.h – спецификация класса СВоок
#pragma once /*Чтобы компилятор
class CBook
{
private: // спецификатор доступа private действует по умолчанию,
// его можно опускать
char m_author [50]; // автор
char *m_pTitle; // указатель на название
int m_year; // год издания
public:
// методы установки значений
void setAuthor const char*);
void setTitle (const char*);
void setYear (const int);
// методы возврата значений
char* getAuthor void ;
char* getTitle (void);
int getYear (void);
}
Классы
Пример: объявление (или спецификация) класса:
И+ПРГ
Классы
Данные и функции класса.
Данные класса называются: поля (данные-члены, компонентные данные).
Функции класса
Классы
Данные и функции класса.
Данные класса называются: поля (данные-члены, компонентные данные).
Функции класса
Поля и методы – это элементы класса.
Поля класса:
могут иметь любой тип, кроме типа этого же класса (но могут быть указателями или ссылками на этот класс);
могут быть описаны с модификатором const, при этом они инициализируются только один раз (с помощью конструктора) и не могут изменяться;
могут быть описаны с модификатором static, но не как auto, extern и register.
Инициализация полей при описании не допускается.
Классы могут быть глобальными (объявленными вне любого блока) и локальными (объявленными внутри блока, например, функции или другого класса).
В каждом классе есть хотя бы один метод, имя которого совпадает с именем класса. Он называется конструктором и вызывается автоматически при создании объекта класса. Конструктор предназначен для инициализации объекта. Автоматический вызов конструктора позволяет избежать ошибок, связанных с использованием неинициализированных переменных.
И+ПРГ
Классы и объекты
Конкретные переменные типа класс называются экземплярами класса, или объектами.
Классы и объекты
Конкретные переменные типа класс называются экземплярами класса, или объектами.
При создании каждого объекта выделяется память, достаточная для хранения всех его полей, и автоматически вызывается конструктор, выполняющий их инициализацию. Методы класса не тиражируются. При выходе объекта из области действия он уничтожается, при этом автоматически вызывается деструктор.
Доступ к элементам объекта аналогичен доступу к полям структуры. Для этого используются операция . (точка) при обращении к элементу через имя объекта и операция -> при обращении через указатель, например:
int n = Vasia.get_ammo(); stado[5].draw;
cout << beavis->get_health();
Обратиться таким образом можно только к элементам со спецификаторам public. Получить или изменить значения элементов со спецификатором private можно только через обращение к соответствующим методам.
Каждый объект содержит свой экземпляр полей класса. Методы класса находятся в памяти в единственном экземпляре и используются всеми объектами совместно, поэтому необходимо обеспечить работу методов с полями именно того объекта, для которого они были вызваны. Это обеспечивается передачей в функцию скрытого параметра this, в котором хранится константный указатель на вызвавший функцию объект. Указатель this неявно используется внутри метода для ссылок на элементы объекта. В явном виде этот указатель применяется в основном для возвращения из метода указателя (return this;) или ссылки (return *this;) на вызвавший объект.
Указатель this можно также применять для идентификации поля класса в том случае, когда его имя совпадает с именем формального параметра метода.
И+ПРГ
Классы и объекты
Описание объекта задает его тип (имя класса) и, возможно,
Классы и объекты
Описание объекта задает его тип (имя класса) и, возможно,
Когда объект объявляется, то согласно описанию класса для объекта происходит выделение оперативной памяти, а также при указании значений данных осуществляется инициализация членов-данных указанными значениями. Всю эту работу делает специальный метод класса, называемый конструктором.
Основные форматы объявления объекта:
<имя_класса> <имя_объекта>;
<имя_класса> <имя_объекта> (список параметров);
<имя_класса> <имя_объект > (имя_объекта_копирования);
Пример объявления объектов класса:
- CBook book, аВоок [100];
- СВоок obj ("Carrol L.", "Alice in Wonderland", 2000);
- CBook copy (obj);
Здесь использованы все три формата для объявления объектов типа класса CBоок Согласно первому, объявляется объект book и массив из 100 объектов аВоок. По второму формату объявлен объект obj, и по третьему -- объект сору.
Объявление объектов класса
И+ПРГ
Вложенные классы
Классы
Один класс можно определить внутри другого ‒ это вложенные классы.
Вложенные классы
Классы
Один класс можно определить внутри другого ‒ это вложенные классы.
Локальные классы
Класс можно определить внутри функции. Пример:
#include
using namespace std;
void f ();
int main() {
f ();
// Класс myclass отсюда не виден
return 0; }
void f ()
{ class myclass
{
int i;
public:
void put_i(int n) { i=n; }
int get_i() { return i; }
} ob;
ob.put_i(10);
cout << b.get_i(); }
На локальные классы налагается несколько ограничений. Во-первых, все функции-члены должны определяться внутри объявления локального класса. Во-вторых, локальные классы не могут обращаться к локальным переменным, объявленным внутри функции, за исключением статических локальных переменных (static) и внешних переменных (extern). Однако они имеют доступ к именам типов и перечислениям, определенным во внешней функции. В-третьих, внутри локальных классов нельзя объявлять статические переменные.
И+ПРГ
Пространства имен предназначены для локализации имен идентификаторов и предотвращения их конфликтов.
Пространства имен предназначены для локализации имен идентификаторов и предотвращения их конфликтов.
Среда программирования С++ работает с большим количеством переменных, функций и классов. Раньше все их имена пребывали в глобальном пространстве и нередко конфликтовали между собой. Чаше всего конфликты имен возникали, когда программа использовала несколько сторонних библиотек одновременно. Особенно это касается имен классов.
Введение ключевого слова namespace позволило решить эти проблемы.
Поскольку пространство имен позволяет локализовать область видимости объектов, объявленных внутри него, одно и то же имя, упомянутое в разных контекстах, больше не вызывает конфликтов. Наибольшую пользу это нововведение принесло стандартной библиотеке языка С++. До сих пор вся стандартная библиотека языка С++ находилась в глобальном пространстве имен (которое, собственно говоря, было единственным). Теперь стандартная библиотека определена внутри своего собственного пространства имен std, что намного уменьшает вероятность конфликтов.
Программист может создавать свои собственные пространства имен и самостоятельно локализовать имена, которые могут вызывать конфликты. Это особенно важно при разработке классов или библиотек функций.
Пространство имён
И+ПРГ
Ключевое слово namespace позволяет разделить глобальное пространство имен на декларативные области
Ключевое слово namespace позволяет разделить глобальное пространство имен на декларативные области
Общий вид объявления пространства имен таков:
namespace <имя_пространства_имён>
{
// Объявления
}
Все, что объявлено в разделе namespace, находится внутри области видимости этого пространства имен.
Пространство имен должно объявляться вне всех остальных областей видимости за исключением того, что одно пространство имен может быть вложено в другое.
То есть вложенным пространство имен может быть только в другое пространство имен, но не в какую бы то ни было иную область видимости.
Это означает, что нельзя объявлять пространства имен, например, внутри функции.
Пространство имён
И+ПРГ
Директива using
Пространство имён
Текст программы, в которой часто встречаются ссылки на
Директива using
Пространство имён
Текст программы, в которой часто встречаются ссылки на
Чтобы избежать этого, следует применять директиву using:
using namespace <имя_пространства_имён>;
using <имя_простр_имён> :: <член_простр_имён>;
В первом варианте параметр
Во втором варианте в область видимости включается только конкретный элемент пространства имен.
Если в программе, например, объявлено пространство имен CounterNameSpace, то можно применить следующие операторы:
using CounterNameSpace::lowerbound; // Видимым является
// только член lowerbound.
lowerbound =10; // Правильный оператор, поскольку переменная
// lowerbound является видимой.
using namespace CounterNameSpace; //Все члены видимы.
upperbound = 100; // Правильный оператор, поскольку все члены
// являются видимыми.
И+ПРГ
ООП. Наследование
Механизм наследования классов позволяет строить иерархии, в которых производные классы
ООП. Наследование
Механизм наследования классов позволяет строить иерархии, в которых производные классы
При большом количестве никак не связанных классов управлять ими становится невозможным. Наследование позволяет справиться с этой проблемой путем упорядочивания и ранжирования классов, то есть объединения общих для нескольких классов свойств в одном классе и использования его в качестве базового.
Классы, находящиеся ближе к началу иерархии, объединяют в себе наиболее общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных черт. Множественное наследование позволяет одному классу обладать свойствами двух и более родительских классов.
Класс лежащий в основе иерархии, называется базовый (base class) (предок, суперкласс), а класс, наследующий свойства базового класса, -- производным (derived class) (наследник, подкласс). Производные классы, в свою очередь, могут быть базовыми по отношению к другим классам.
Объявление наследования класса:-
class <имя_производного_класса> :
<уровень_доступа имя_базового_класса>
{
<тело_класса>
} ;
уровень_доступа – это public, private или protected. По умолчанию для производного класса private, а для производной структуры – public. Член класса со спецификатором protected недоступен вне класса, но может наследоваться производным классом.
И+ПРГ
ООП. Наследование
#include
class building
{ int rooms; int floors; int area;
public:
ООП. Наследование
#include
class building
{ int rooms; int floors; int area;
public:
int get_rooms();
void set_floors (int num);
int get_floors();
void set_area (int num);
int get_area(); };
// Класс house – производный от building
class house : public building
{ int bedrooms; int baths;
public:
void set_bedrooms (int num);
int get_bedrooms();
void set_baths (int num);
int get_baths(); };
// Класс school – также производный от building
class school : public building
{ int classrooms; int offices;
public:
void set_classrooms (int num);
int get_classrooms();
void set_offices (int num);
int get_offices(); };
void building :: set_rooms (int num)
{ rooms = num; }
void building :: set_floors (int num)
{ floors = num; }
// см. продолжение
// продолжение
void building :: set_area (int num)
{ area = num; }
int building :: get_rooms ()
{ return rooms; }
int building :: get_rooms ()
{ return rooms; }
int building :: get_floors ()
{ return floors; }
int building :: get_area ()
{ return area; }
void house :: set_bedrooms (int num)
{ bedrooms = num; }
void house :: set_baths (int num)
{ baths= num; }
int house :: get_bedrooms ()
{ return bedrooms; }
int house :: get_baths ()
{ return baths; }
void school :: set_classrooms (int num)
{ classrooms= num; }
void school :: set_offices (int num)
{offices= num; }
int school :: get_classrooms ()
{ return classrooms; }
int school :: get_offices ()
{ return offices ; }
// см. продолжение
Пример
И+ПРГ
ООП. Наследование
// продолжение
int main ()
{
house h;
school s;
h.set_rooms (12);
h.set_floors (3);
h.set_area
ООП. Наследование
// продолжение
int main ()
{
house h;
school s;
h.set_rooms (12);
h.set_floors (3);
h.set_area
h.set_bedrooms (5);
h.set_baths (3);
cout << "В доме " << h.get_bedrooms ();
cout << " спален\n";
s.set_rooms (200);
s.set_classrooms (180);
s.set_offices (5);
s.set_area (25000);
cout << "В школе" << s.get_classrooms ();
cout << " кабинетов\n";
cout <<"Её площадь равна " << s.get.area ();
return 0;
}
Пример
Вывод на экран:
В доме 5 спален
В школе 180 кабинетов
Её площадь равна 25000
Замечания:
Открытые члены класса building становятся открытыми членами производных классов house и school.
НО, методы (функции-члены) классов house и school не имеют доступа к закрытым членам класса building.
То есть, наследование не нарушает инкапсуляцию.
И+ПРГ
ООП. Наследование
Простое (или одиночное) наследование – это наследование, при котором производный
ООП. Наследование
Простое (или одиночное) наследование – это наследование, при котором производный
В Примере на предыдущих слайдах как раз реализовано простое наследование. Формально наследование одного класса от другого можно задать следующей конструкций:
сlass имя_класса_потомка: [модификатор_доступа]
имя_базового_класса
{ тело__класса }
Класс-потомок наследует структуру (все элементы данных) и поведение (все методы) базового класса. Модификатор доступа определяет доступность элементов базового класса в классе-наследнике. Этот модификатор мы будем называть модификатором наследования.
Если в качестве модификатора наследования записано слово public, то такое наследование открытое. При использовании модификатора protected – защищенное наследование, а private означает закрытое наследование.
И+ПРГ
ООП. Наследование
Множественное наследование отличается от простого (одиночного) наличием нескольких базовых классов:
class
ООП. Наследование
Множественное наследование отличается от простого (одиночного) наличием нескольких базовых классов:
class
class D: public A, public В
Базовые классы перечисляются через запятую; количество их стандартом не ограничивается. Модификатор наследования для каждого базового класса может быть разный: можно от одного класса наследовать открыто, а от другого ‒ закрыто.
При множественном наследовании выполняется все то же самое, что и при одиночном, то есть класс-потомок наследует структуру (все элементы данных) и поведение (все методы) всех базовых классов.
И+ПРГ
ООП. Наследование
Конструкторы при наследовании
Конструкторы не наследуются, поэтому производный класс должен иметь
ООП. Наследование
Конструкторы при наследовании
Конструкторы не наследуются, поэтому производный класс должен иметь
Порядок вызова конструкторов определяется приведенными ниже правилами:
Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса по умолчанию (то есть тот, который можно вызвать без параметров).
Для иерархии, состоящей из нескольких уровней, конструкторы базовых классов вызываются начиная с самого верхнего уровня. После этого выполняются конструкторы тех элементов класса, которые являются объектами, в порядке их объявления в классе, а затем исполняется конструктор класса.
В случае нескольких базовых классов их конструкторы вызываются в порядке объявления.
ВНИМАНИЕ!
Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации.
И+ПРГ
ООП. Наследование
Деструкторы при наследовании
Правила наследования деструкторов
Деструкторы не наследуются, и если
ООП. Наследование
Деструкторы при наследовании
Правила наследования деструкторов
Деструкторы не наследуются, и если
В отличие от конструкторов, при написании деструктора производного класса в нем не требуется явно вызывать деструкторы базовых классов, поскольку это будет сделано автоматически.
Для иерархии классов, состоящей из нескольких уровней, деструкторы вызываются в порядке, строго обратном вызову конструкторов: сначала вызывается деструктор класса, затем ‒ деструкторы элементов класса, а потом деструктор базового класса.
И+ПРГ
Полиморфизм – возможность использовать в различных классах иерархии одно имя для
Полиморфизм – возможность использовать в различных классах иерархии одно имя для
один интерфейс, несколько методов.
Полиморфизм в ООП реализуется с помощью нескольких инструментов:
Два вида перегрузки: перегрузка функций и перегрузка операторов;
Шаблоны: шаблоны классов и шаблоны функций;
Переназначение функций при наследовании: виртуальные функции и абстрактные классы.
Пример полиморфизма первой разновидности ̶ перегрузка функций, когда из нескольких заданных вариантов функции выбирается наиболее подходящая по соответствию ее прототипа передаваемым параметрам.
С перегрузкой функций тесно связан механизм перегрузки операторов (операций). Перегруженный оператор сохраняет своё первоначальное предназначение, Просто набор типов данных, к которым его можно применять, расширяется.
Например, в классе stack можно перегрузить оператор "+" для заталкивания элемента в стек, а оператор "-" для выталкивания элемента из стека.
Второй пример – использование шаблонов функций (и шаблонов классов), когда один и тот же алгоритм видоизменяется в соответствии с заданным типом данных. переданным в качестве параметра при вызове. В этой разновидности полиморфизма создаётся шаблон алгоритма с обобщённым(-ми) типами данных некоторых переменных, которые конкретизируются при вызове в программе.
Третья разновидность полиморфизма – виртуальные функции - используется в схеме наследования и позволяет определить в классе-наследнике другой алгоритм выполнения одноимённого члена-метода (функции).
ООП. Полиморфизм.
ООП
Перегрузка функций (function overloading) – это использование одного имени для нескольких
Перегрузка функций (function overloading) – это использование одного имени для нескольких
Перегрузка функций является особенностью языка C++, которой нет в языке С, это одна из разновидностей полиморфизма.
Основные концепции перегрузки функций:
Перегрузка функций предоставляет несколько "взглядов" на одну и ту же функцию внутри вашей программы.
Для перегрузки функций просто надо определить несколько функций с одним и тем же именем, которые отличаются только количеством и/или типом параметров.
Компилятор C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых (фактических) параметров.
Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.
Правила описания перегруженных функций:
Перегруженные функции должны находиться в одной области видимости,
Перегруженные функции могут иметь параметры по умолчанию, при этом значения одного и того же параметра в разных функциях должны совпадать,
Функции не могут быть перегружены, если описание их параметров отличается только модификатором const или использованием ссылки (например, int и const int или int и int&).
Перегрузка функций
ООП. Полиморфизм.
ООП
Перегрузка функций
ПРИМЕР: надо сделать функции поиска в базе данных, каждая функции
Перегрузка функций
ПРИМЕР: надо сделать функции поиска в базе данных, каждая функции
int Search_int(int Num);
int Search_long(long Num);
int Search_float(float Num);
int Search_double(double Num);
Но гораздо проще дать всем этим функциям одно имя для поиска всех типов данных. Например:
int Search(int Num);
int Search(long Num);
int Search(float Num);
int Search(double Num);
Заметим - имена функций одинаковы, отличие только в типах аргументов. Цель перегрузки функций состоит в том, чтобы функции с одним именем ВЫПОЛНЯЛИСЬ ПО-РАЗНОМУ (и возможно возвращали разные значения) при обращении к ним с разными по типам и количеству параметрами. В языке С++ функции могут иметь одинаковые имена до тех пор, пока они значимо отличаются хотя бы одним параметром. Если значимого различия нет – компилятор предупредит о возникшей неопределенности.
Необходимо отличать перегрузку и переопределение функции.
Если функции возвращают одинаковый тип и список параметров у них абсолютно одинаковый, то второе объявление функции будет обработано как повторное определение. Если списки параметров двух функций абсолютно одинаковы, но отличаются только типы возврата, то второе объявление - ошибка:
int Search(int Num);
long Search(int Num); //ошибка!
ООП. Полиморфизм.
ООП
Перегрузка функций
Как компилятор решает, какую именно функцию надо использовать для данного
Перегрузка функций
Как компилятор решает, какую именно функцию надо использовать для данного
Компилятор находит соответствие автоматически, сравнивая фактические параметры, используемые в запросе, с параметрами, предлагаемыми каждой функцией из набора перегруженных функций. Рассмотрим набор функций и один вызов функции:
void calc();
void calc(int);
void calc(int, int);
void calc(double, double=1.2345);
calc(5.4321); //вызвана будет функция void calc(double, double)
Как в этом случае компилятор ведет поиск соответствия функции?
Сначала определяются функции-кандидаты на проверку соответствия. Функция-кандидат должна иметь то же имя, что и вызываемая функция, и ее объявление должно быть видимым в точке запроса.
Затем определяются "жизнеспособные" функции. Они должны или иметь то же самое количество параметров что и в запросе, или тип каждого параметра должен соответствовать параметру в запросе (или быть конвертируемым типом).
Для нашего запроса calc(5.4321), мы можем сразу выкинуть функции-кандидаты calc() и calc(int, int). Запрос имеет только один параметр, а эти функции имеют нуль и два параметра, соответственно. calc(int) - вполне "жизнеспособная" функция, потому что можно конвертировать тип параметра double к типу int. Функция calc(double, double) тоже "жизнеспособная", потому что заданный по умолчанию параметр обеспечивает "якобы недостающий" второй параметр функции, а первый параметр имеет тип double, который точно соответствует типу параметра в вызове.
Потом компилятор определяет, какая из найденных "жизнеспособных" функций имеет "явно лучшее" соответствие фактическим параметрам в запросе. Что понимать под "явно лучшим"? Идея состоит в том, что чем ближе типы параметров друг к другу, тем лучше соответствие. То есть, точное соответствие типа лучше чем соответствие, которое требует преобразования типа.
В нашем запросе calc(5.4321) - только один явный параметр, и он имеет тип double. Чтобы вызвать функцию calc(int), параметр должен быть преобразован в int. Функция calc(double, double) является более точным соответствием для этого параметра. И именно эту функцию использует компилятор.
ООП. Полиморфизм.
ООП
Перегрузка функций
Примеры
// Различные типы данных параметров:
// выводится на экран значение параметра
#include
Перегрузка функций
Примеры
// Различные типы данных параметров:
// выводится на экран значение параметра
#include
// прототипы функций
int refunc(int i);
double refunc(double i);
int main()
{
cout << refunc(10) << " ";
cout << refunc(5.4);
return 0;
}
// Функция для целых типов данных
int refunc(int i)
{ return i; }
// Функция для данных двойной точности
double refunc(double i)
{ return i; }
// Различное количество параметров: ООП. Полиморфизм. ООП
// выводится при одном параметре значение // параметра, а при 2-х – произведение их
#include
// прототипы функций
int refunc(int i);
int refunc(int i, int j);
int main()
{
cout << refunc(10) << " ";
cout << refunc(5, 4);
return 0;
}
// Функция для одного параметра
int refunc(int i)
{ return i; }
// Функция для двух паарметров
int refunc(int i, int j)
{ return i*j; }
Перегрузка операторов (operator overloading) – это изменение смысла оператора, при котором
Перегрузка операторов (operator overloading) – это изменение смысла оператора, при котором
Например, оператора плюс (+), который обычно используется для сложения, при использовании его с определенным классом меняет смысл.
Для объектов класса string оператор плюс (+) будет добавлять указанные символы к текущему содержимому строки, а оператор минус (-) будет удалять каждое вхождение указанного символа из строки.
Перегрузка операторов осуществляется с помощью операторных функций (operator function), которые определяют действия перегруженных операторов применительно к соответствующему классу. Операторные функции создаются с помощью ключевого слова operator. Операторные функции могут быть как членами класса, так и обычными функциями. Однако обычные операторные функции, как правило, объявляют дружественными по отношению к классу, для которого они перегружают оператор.
Перегрузка операторов
ООП. Полиморфизм.
ООП
Операторная функция-член имеет следующий вид:
<тип_возвращаемого_значения> <имя_класса> :: operator# (список-аргументов)
{
... //
Операторная функция-член имеет следующий вид:
<тип_возвращаемого_значения> <имя_класса> :: operator# (список-аргументов)
{
... //
}
Обычно операторная функция возвращает объект класса, с которым она работает, однако тип возвращаемого значения может быть любым. Символ # заменяется перегружаемым оператором.
Например, если в классе перегружается оператор деления "/", операторная функция-член называется operator/.
При перегрузке унарного оператора список аргументов остается пустым.
При перегрузке бинарного оператора список аргументов содержит один параметр.
Перегрузка операторов
ООП. Полиморфизм.
ООП
Программа создает класс lос, в котором хранятся географические координаты: широта и
Программа создает класс lос, в котором хранятся географические координаты: широта и
Перегрузка операторов
ООП. Полиморфизм.
ООП
ПРИМЕР
#include Продолжение
using namespace std;
class loc
{
int longitude, latitude;
public:
loc() {}
loc(int lg, int lt)
{
longitude = lg;
latitude = lt;
}
void show()
{
cout << longitude << " ";
cout << latitude << "\n";
}
loc operator+(loc op2);
}; см. продолжение
// Overload + for loc.
loc loc :: operator+(loc op2)
{
loc temp;
temp.longitude = op2.longitude + longitude;
temp.latitude = op2.latitude + latitude;
return temp;
}
int main()
{
loc ob1(10, 20), ob2( 5, 30);
ob1.show(); // Выводит на экран числа 10 20
ob2.show(); // Выводит на экран числа 5 30
ob1 = ob1 + ob2;
ob1.show(); // Выводит на экран числа 15 50
return 0;
}
Ограничения на перегруженные операторы
Во-первых, нельзя изменить приоритет оператора.
Во-вторых, невозможно изменить
Ограничения на перегруженные операторы
Во-первых, нельзя изменить приоритет оператора.
Во-вторых, невозможно изменить
В-третьих, операторную функцию нельзя вызывать с аргументами, значения которых заданы по умолчанию.
И, в заключение, нельзя перегружать следующие операторы:
оператор выбора члена класса .
оператор селектора члена класса .*
оператор разрешения области видимости ::
тернарный оператор – условная операция ?:
статический оператор вычисления длины оператора в байтах sizeof
Кроме перечисленных операторов, относящихся к синтаксису языка С++, не разрешается переопределять следующие операторы препроцессора:
оператор превращения в строку #
оператор конкатенации ##
Примечание
Операторы # и ## используются в директиве препроцессора #define, которую современный стандарт С++ применять не рекомендует. Однако следует помнить о невозможности их перегрузки для классов.
За исключением оператора "=", операторные функции наследуются производными классами. Однако в производном классе каждый из этих операторов снова можно перегрузить.
Перегрузка операторов
ООП. Полиморфизм.
ООП
Шаблоны в С++
ООП
С помощью шаблонов (templates) можно создавать родовые (обобщённые) функции
Шаблоны в С++
ООП
С помощью шаблонов (templates) можно создавать родовые (обобщённые) функции
Шаблон представляет собой функцию (template function) или класс (template class), реализованные для одного или нескольких типов данных, которые не известны в момент написания кода. При использовании шаблона в качестве аргументов ему явно или неявно передаются конкретные типы данных. Поскольку шаблоны являются средствами языка, для них обеспечивается полная поддержка проверки типов и областей видимости.
Таким образом, шаблоны дают возможность создавать многократно используемые программы.
Поскольку шаблоны являются логическим развитием механизма макроподстановок, то данные о типах, например длина операндов, подставляется на этапе компиляции (точнее, даже еще раньше). На момент выполнения все длины операндов, размеры элементов массивов и прочие величины уже вычислены, так что процессор работает с хорошо оптимизированным прямолинейным кодом.
ООП
Обобщенная функция (шаблон функции – template function) определяет универсальную совокупность операций,
ООП
Обобщенная функция (шаблон функции – template function) определяет универсальную совокупность операций,
Многие алгоритмы носят универсальный характер и не зависят от типа данных, которыми они оперируют. Например, для массивов целых и действительных чисел используется один алгоритм быстрой сортировки. С помощью обобщенной функции можно определить алгоритм независимо от типа данных. После этого компилятор автоматически генерирует правильный код, соответствующий конкретному типу. По существу, обобщенная функция автоматически перегружает саму себя.
Обобщенная функция объявляется с помощью ключевого слова template.
Создается шаблон, который описывает действия функции и позволяет компилятору самому уточнять необходимые детали.
Определение шаблонной функции выглядит следующим образом:
template
<имя_функции> (<список_параметров>)
{
// Тело функции
}
Здесь параметр Ттип – имя типа – задает тип данных, с которым работает функция. Этот параметр можно использовать и внутри функции, однако при создании конкретной версии обобщенной функции компилятор автоматически подставит вместо него фактический тип. Традиционно обобщенный тип задается с помощью ключевого слова class, хотя вместо него можно применять ключевое слово typename.
Шаблоны функций
ООП
Обобщенная функция, меняющая местами две переменные.
Поскольку процесс перестановки не
ООП
Обобщенная функция, меняющая местами две переменные.
Поскольку процесс перестановки не
Шаблоны функций
// Пример шаблонной функции int main() продолжение Строка
#include
using namespace std;
// Шаблон функции
template
void swapargs(X &a, X &b)
{
X temp;
temp = a; a = b; b = temp;
} см. продолжение
{ int i=10, j=20;
double x=10.1, y=23.3;
char a='x'; b='z';
cout << "Исходные значения i, j: " << i << ' '
<< j << '\n';
cout << "Исходные значения x, y: " << x <<
' ' << у << '\n';
cout << "Исходные значения a, b: " << a <<
' ' << b << '\n';
swapargs (i, j); // Перестановка целых чисел swapargs(x, у); // Перестановка действительных // чисел
swapargsfa, b); // Перестановка символов.
cout << "Переставленные значения i, j: " << i <<' ' << j <<'\n';
cout << "Переставленные значения x, у: " << x << ' ' << у <<'\n';
cout << "Переставленные значения a, b: " << a << ' ' << b << ' \n';
return 0;
}
template
сообщает компилятору, что: во-первых, создается шаблон, во-вторых, начинается описание обобщен-ной функции.
Здесь параметр X задает обобщенный тип, который впоследствии будет заменен фактическим типом. После этой строки объявляется функция swapargs(), в которой переменные, подлежащие перестановке, имеют обобщенный тип X. В функции main() функция swapargs() вызывается для трех разных типов данных: int, double и char. Поскольку функция swapargs() является обобщенной, компилятор автоматически создает три ее версии: для перестановки целых и действительных чисел, а также символов.
ООП
Уточним понятия связанные с шаблонами.
Обобщенная функция (т.е. функция,
ООП
Уточним понятия связанные с шаблонами.
Обобщенная функция (т.е. функция,
Конкретная версия обобщенной функции, создаваемая компилятором, называется специализацией (specialization) или генерируемой функцией (generated function). Процесс генерации конкретной функции называется конкретизацией (instantiation). Генерируемая функция является конкретным экземпляром обобщенной функции.
Шаблоны функций
Поскольку язык С++ не считает конец строки символом конца оператора, раздел template в определении обобщенной функции не обязан находиться в одной строке с именем функции. НО, в этом случае следует помнить, что между оператором template и началом определения обобщенной функции не должно быть других операторов. Например, следующий фрагмент ошибочен.
Такую особенность иллюстрирует следующий пример:
template
void swapargs(X &а, X &b)
{
X temp;
temp = a; a = b; b = temp;
}
// Этот фрагмент содержит ошибку:
template
int i; // Ошибка
void swapargs(X &a, X &b)
{
X temp;
temp = a; a = b; b = temp;
}
Т.е., спецификация template должна непосредственно предшествовать определению функции.
ООП
Функция с двумя обобщенными типами
Шаблоны функций
Используя список, элементы которого разделены
ООП
Функция с двумя обобщенными типами
Шаблоны функций
Используя список, элементы которого разделены
Например, в следующей программе создается шаблонная функция, имеющая два обобщенных типа.
#include
using namespace std;
template
void myfunc(type1 x, type2 y)
{
cout << x << ' ' << у << '\n';
}
int main()
{
myfunc(10, "Я люблю С++");
myfunc(98.6, 19L);
return 0;
}
ООП
Ограничения на обобщенные функции
Шаблоны функций
Обобщенные функции напоминают перегруженные, но на
ООП
Ограничения на обобщенные функции
Шаблоны функций
Обобщенные функции напоминают перегруженные, но на
РЕЗЮМЕ
Шаблонная функция (template function) ‒ это функция, полностью контролирующая соответствие типов данных, которые задаются ей как параметры.
Объявление шаблона функции повторяет определение обычной функции, но первой строкой в объявлении обязательно должна быть строка следующего вида:
template
В угловых скобках < > после ключевого слова template указывается список формальных параметров шаблона. Имя_типа называют параметром типа, который представляет собой идентификатор типа и используется для определения типа параметра в заголовке шаблонной функции, типа возвращаемого функцией значения и типа переменных, объявляемых в теле функции. Каждый параметр типа должен иметь уникальный идентификатор, который может быть использован в разных шаблонах. Каждому параметру типа должно предшествовать ключевое слово class.
За строкой template следует строка со стандартным объявлением функции, где в списке параметров используются как параметры типа, так и любые другие допустимые базовые или производные типы данных.
Шаблонная функция может быть перегружена другим шаблоном или не шаблонной функцией с таким же именем, но с другим набором параметров.
Компилятор выбирает вариант функции, соответствующий вызову. Сначала подбирается функция, которая полностью соответствует по имени и типам параметров вызываемой функции. Если попытка заканчивается неудачей, подбирается шаблон, с помощью которого можно сгенерировать функцию с точным соответствием типов всех параметров и имени. Если же и эта попытка неудачна, выполняется подбор перегруженной функции.
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
#include
using namespace std;
template
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
#include
using namespace std;
template
void bubble (
X *items, // Указатель на упорядочиваемый массив
int count) // Количество элементов массива
{
register int a, b;
X t;
for(a=1; a
if(items[b-1] > items[b])
// Перестановка элементов
{ t = items[b-1]; items[b-1] = items[b]; items[b] = t; }
}
Сортировка представляет собой типичный пример универсального алгоритма. Как правило, алгоритм сортировки совершенно не зависит от типа сортируемых данных.
Функция bubble (), предназначенная для сортировки данных методом пузырька, упорядочивает массив любого типа. Ей передается указатель на первый элемент массива и количество элементов в массиве. Несмотря на то что этот алгоритм сортировки является одним из самых медленных, он очень прост и нагляден.
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
int main()
{
int
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
int main()
{
int
double darray[5] = {4.3, 2.5, -0.9, 100.2, 3.0};
int i;
cout << "Неупорядоченный массив целых чисел: ";
for(i=0; i<7; i++)
cout << iarray[i] << ' ';
cout << endl;
cout << "Неупорядоченный массив действительных чисел: ";
for(i=0; i<5; i++)
cout << darray[i] << ' ';
cout << endl;
bubble(iarray, 7); bubble(darray, 5);
cout « "Упорядоченный массив целых чисел: ";
for(i=0; i<7; i++)
cout << iarray[i] << ' ';
cout << endl;
cout << "Упорядоченный массив действительных чисел: ";
for(i=0; i<5; i++)
cout << darray[i] << ' ';
cout << endl;
return 0;
}
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
Программа выводит на экран следующие
ООП
Шаблоны функций
Применение обобщенных функций. Обобщенная сортировка
Программа выводит на экран следующие
Неупорядоченный массив целых чисел: 7 5 4 3 9 8 6
Неупорядоченный массив действительных чисел: 4.3 2.5 -0.9 100.2 3
Упорядоченный массив целых чисел: 3 4 5 6 7 8 9
Упорядоченный массив действительных чисел: -0.9 2.5 3 4.3 100.2
Как видим, в программе создаются два массива, имеющие соответственно типы int и double.
Поскольку функция bubble() является шаблонной, она автоматически перегружается для каждого из этих типов.
Эту функцию можно применить для сортировки данных другого типа, даже объектов какого-нибудь класса. Компилятор автоматически создаст соответствующую версию функции для нового типа.
ООП
При наследовании часто бывает необходимо, чтобы поведение некоторых методов базового класса
ООП
При наследовании часто бывает необходимо, чтобы поведение некоторых методов базового класса
Виртуальные функции
#include В базовом классе определены два метода ‒ f() и) CallFunction(), причем во втором методе вызывается первый. В классе-наследнике метод f() переопределен, а метод CallFunction() унаследован. Очевидно, метод f() переопределяется для того, чтобы объекты базового класса и класса-наследника вели себя по-разному.
using namespace std;
class Base // базовый класс
{ public:
int f(const int &d) //метод базового класса
{ return 2*d; }
int CallFunction(const int &d)
{return f(d)+l;} // вызов метода базового класса
};
Class Derived: public Base // производный класс
{ public: // CallFunction наследуется
int f(const int &d) // метод f переопределяется
{ return d*d; }
};
int main()
{ Base a; // объект базового класса
cout<
cout << b.CallFunction(5)<< endl; // какой // метод f вызывается?
return 0;
Объявляя объект b типа Derived, программист, естественно, ожидает получить результат 5*5 + 1 = 26 ‒ для этого и переопределялся метод f().
Однако на экран, как и для объекта а типа Base, выводится число 11, которое, очевидно, вычисляется как 2*5+1 = 11.
Несмотря на переопределение метода f() в классе-наследнике, в унаследованной функции CallFunction() вызывается «родная» функция f(), определенная в базовом классе!
При трансляции класса Base компилятор ничего не знает о классах-наследниках, поэтому он не может предполагать, что метод f() будет переопределен в классе Derived.
Его естественное поведение ‒ «прочно» связать вызов f() с телом метода класса Base.
ООП
Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию-метод
ООП
Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию-метод
Виртуальная функция (virtual function) ‒ это функция-член, объявленная в базовом классе и переопределенная в производном.
Ключевое слово virtual указывается до объявления функции в базовом классе.
Производный класс переопределяет эту функцию, приспосабливая ее для своих нужд. По существу, виртуальная функция реализует принцип "один интерфейс, несколько методов", лежащий в основе полиморфизма.
Виртуальная функция в базовом классе определяет вид интерфейса, т.е. способ вызова этой функции. Каждое переопределение виртуальной функции в производном классе реализует операции, присущие лишь данному классу. Иначе говоря, переопределение виртуальной функции создает конкретный метод (specific method).
При обычном вызове виртуальные функции ничем не отличаются от остальных функций-членов. Особые свойства виртуальных функций проявляются при их вызове с помощью указателей. Указатели на объекты базового класса можно использовать для ссылки на объекты производных классов. Если указатель на объект базового класса устанавливается на объект производного класса, содержащий виртуальную функцию, выбор требуемой функции основывается на типе объекта, на который ссылается указатель, причем этот выбор осуществляется в ходе выполнения программы. Таким образом, если указатель ссылается на объекты разных типов, то будут вызваны разные виртуальные функции. Это относится и к ссылкам на объекты базового класса.
Виртуальные функции
ООП
Таким образом, объявление метода f() в базовом и производном классах должно
ООП
Таким образом, объявление метода f() в базовом и производном классах должно
virtual int f(const int &d) // в базовом классе
{ return 2*d; }
virtual int f(const int &d) // в производном классе
{ return d*d: }
После этого для объектов базового и производного классов мы получаем разные результаты: 11 и 26.
Аналогично в объявление метода print() тоже должно начинаться со слова virtual:
virtual void print() const // в базовом классе
{ cout << "Clock!" << endl: }
virtual void print() const // в производном классе
{ cout << "Alarm!" << endl; }
После этого вызов settime() с параметром базового класса обеспечит вывод на экран слова «С1оск», а с параметром производного класса ‒ слова «Аlarm». И при вызове по указателю наблюдается та же картина.
Вообще-то ключевое слово virtual достаточно написать только один раз ‒ в объявлении функции базового класса. Определение можно писать без слова virtual ‒ все равно функция будет считаться виртуальной. Однако лучше всегда это делать явным образом, чтобы всегда по тексту было видно, что функция является виртуальной.
Для виртуальных функций обеспечивается не статическое, а динамическое (позднее, отложенное) связывание, которое реализуется во время выполнения программы.
В С++ реализованы два типа полиморфизма:
□ статический полиморфизм, или полиморфизм времени компиляции (compile-time polymorphism), осуществляется за счет перегрузки и шаблонов функций;
□ динамический полиморфизм, или полиморфизм времени выполнения (runtime polymorphism), реализуется виртуальными функциями.
Виртуальные функции
ООП
С перегрузкой функций "разбирается" компилятор, правильно подбирая вариант функции в той
ООП
С перегрузкой функций "разбирается" компилятор, правильно подбирая вариант функции в той
Выбор же виртуальной функции происходит динамически ‒ при выполнении программы. Класс, включающий в себя виртуальные функции, называется полиморфным.
Правила описания и использования виртуальных функций-методов:
Виртуальная функция может быть только методом класса,
Любую перегружаемую операцию-метод класса можно сделать виртуальной, например, операцию присваивания или операцию преобразования типа,
Виртуальная функция, как и сама виртуальность, наследуется,
Виртуальная функция может быть константной,
Если в базовом классе определена виртуальная функция, то метод производного класса с такими же именем и прототипом (включая тип возвращаемого значения и константность метода) автоматически является виртуальным (слово virtual указывать необязательно) и замещает функцию-метод базового класса,
Конструкторы не могут быть виртуальными,
Статические методы не могут быть виртуальными,
Деструкторы могут (чаще ‒ должны) быть виртуальными ‒ это гарантирует корректный возврат памяти через указатель базового класса.
Виртуальные функции
ООП
Объясним виртуальные функции ещё раз по-другому:
Работа с объектами чаще всего производится
ООП
Объясним виртуальные функции ещё раз по-другому:
Работа с объектами чаще всего производится
monstr *р: // Описание указателя на базовый класс
р = new daemon; // Указатель ссылается на объект производного класса
Вызов методов объекта происходит в соответствии с типом указателя, а не фактическим типом объекта, на который он ссылается,
поэтому при выполнении оператора, например, p->draw(l, 1. 1. 1);
будет вызван метод класса monstr, а не класса daemon, поскольку ссылки на методы разрешаются во время компоновки программы. Этот процесс называется ранним связыванием.
Чтобы вызвать метод класса daemon, можно использовать явное преобразование типа указателя: (daemon * p)->draw(l, 1, 1. 1);
Но это не всегда возможно, поскольку в разное время указатель может ссылаться на объекты разных классов иерархии, и во время компиляции программы конкретный класс может быть неизвестен. В качестве примера можно привести функцию, параметром которой является указатель на объект базового класса. На его место во время выполнения программы может быть передан указатель на любой производный класс. Другой пример ‒ связный список указателей на различные объекты иерархии, с которым требуется работать единообразно.
В С++ реализован механизм позднего связывания, когда разрешение ссылок на метод происходит на этапе выполнения программы в зависимости от конкретного типа объекта, вызвавшего метод. Этот механизм реализован с помощью виртуальных методов.
Для определения виртуального метода используется спецификатор virtual, например:
virtual void draw(int х. int у, int scale, int position);
Виртуальные функции
ООП
Правила описания и использования виртуальных методов:
□ Если в базовом классе метод
ООП
Правила описания и использования виртуальных методов:
□ Если в базовом классе метод
□ Виртуальные методы наследуются, то есть переопределять их в производном классе требуется только при необходимости задать отличающиеся действия. Права доступа при переопределении изменить нельзя,
□ Если виртуальный метод переопределен в производном классе, объекты этого класса могут получить доступ к методу базового класса с помощью операции доступа к области видимости,
□ Виртуальный метод не может объявляться с модификатором static, но может быть объявлен как дружественный,
□ Если в классе вводится описание виртуального метода, он должен быть определен хотя бы как чисто виртуальный.
Чисто виртуальный метод содержит признак - 0 вместо тела, например:
virtual void f(int) = 0;
Чисто виртуальный метод должен переопределяться в производном классе (возможно, опять как чисто виртуальный).
Виртуальные функции
ООП
Сравнение раннего и позднего связывания
Раннее связывание (early binding) означает события,
ООП
Сравнение раннего и позднего связывания
Раннее связывание (early binding) означает события,
Позднее связывание (late binding) является антиподом раннего. В языке С++ позднее связывание означает, что фактический выбор вызываемой функции осуществляется только в ходе выполнения программы. Основным средством позднего связывания являются виртуальные функции. Выбор вызываемой виртуальной функции зависит от типа указателя или ссылки, с помощью которых она вызывается. Поскольку в большинстве случаев на этапе компиляции эта информация отсутствует, связывание объекта и вызова функции откладывается до выполнения программы. Основное преимущество позднего связывания ‒ гибкость. В отличие от раннего, позднее связывание позволяет создавать программу, реагирующую на события, происходящие в ходе ее выполнения, без использования большого количества кода, предусматривающего всевозможные варианты. Однако позднее связывание может замедлить работу программы.
Виртуальные функции
ООП
Абстрактные классы
Класс, содержащий хотя бы один чисто виртуальный метод, называется абстрактным
ООП
Абстрактные классы
Класс, содержащий хотя бы один чисто виртуальный метод, называется абстрактным
Несмотря на то что объекты абстрактного класса не существуют, можно создать указатели и ссылки на абстрактный класс. Это позволяет применять абстрактные классы для поддержки динамического полиморфизма и выбирать соответствующую виртуальную функцию в зависимости от типа указателя или ссылки.
При определении абстрактного класса необходимо иметь в виду следующее:
абстрактный класс нельзя использовать при явном приведении типов, для описания типа параметра и типа возвращаемого функцией значения;
допускается объявлять указатели и ссылки на абстрактный класс, если при инициализации не требуется создавать временный объект;
если класс, производный от абстрактного, не определяет все чисто виртуальные функции, он также является абстрактным.
Таким образом, можно создать функцию, параметром которой является указатель на абстрактный класс. На место этого параметра при выполнении программы может передаваться указатель на объект любого производного класса. Это позволяет создавать полиморфные функции, работающие с объектом любого тина в пределах одной иерархии.
Виртуальные функции
ООП
Практическое занятие: Вызов виртуальной функции с помощью указателя на объект базового
ООП
Практическое занятие: Вызов виртуальной функции с помощью указателя на объект базового
Виртуальные функции
#include Эта программа выводит на экран следующие строки:
using namespace std;
class base { public:
virtual void vfunc() {
cout<<"Функция vfunc() из класса base.\n"; } };
class derived1 : public base { public:
void vfunc() {
cout << "Функция vfunc() из класса derived1.\n";
} };
class derived2 : public base { public:
void vfunc() {
cout << "Функция vfunc() из класса derived2.\n";
} };
int main()
{ base *p, b; derived1 d1; derived2 d2;
// Указатель на объект базового класса
p = &b;
p->vfunc(); // Вызов функции vfunc() из класса base
// Указатель на объект класса derivedl
p = &d1;
p->vfunc(); // Вызов функции vfunc() из класса
derivedl
// Указатель на объект класса derived2
p = &d2;
p->vfunc(); // Вызов функции vfunc() из класса
derived2
return 0; }
Функция vfunc() из класса base.
Функция vfunc() из класса derived1.
Функция vfunc() из класса derived2.
Как показывает эта программа, внутри класса base объявлена виртуальная функция vfunc(). Обратите внимание на ключевое слово virtual в объявлении функции. При переопределении функции vfunc() в классах derived1 и derived2 ключевое слово virtual не требуется. (Однако его использование не является ошибкой, просто оно не обязательно.)
В данной программе классы derived1 и derived2 являются производными от класса base. Внутри каждого из этих классов функция vfunc() переопределя-ется заново в соответствии с новым предназначением. В программе main() объявлены четыре переменные:
ООП
Виртуальные функции
Указателю р присваивается адрес объекта b, а функция vfunc()
ООП
Виртуальные функции
Указателю р присваивается адрес объекта b, а функция vfunc()
Затем указателю р присваивается адрес объекта d1, и функция vfunc() снова вызывается с его помощью. На этот раз указатель р ссылается на объект класса derived1. Следовательно, вызывается функция
derived1::vfunc(). В результате указателю р присваивается адрес объекта d2, поэтому выражение p->vfunc() приводит к вызову функции vfunc() из класса derived2. Принципиально важно, что вариант вызываемой функции определяется типом объекта, на который ссылается указатель р. Кроме того, выбор происходит в ходе выполнения программы, что обеспечивает основу динамического полиморфизма.
Виртуальную функцию можно вызывать обычным способом, используя имя объекта и оператор однако полиморфизм достигается только при обращении к ней через указатель.
Например, следующий фрагмент программы является совершенно правильным:
d2.vfunc(); // Вызывается функция vfunc() из класса derived2.
Несмотря на то что такой вызов виртуальной функции ошибкой не является, никаких преимуществ он не предоставляет.
Практическое занятие:
Вызов виртуальной функции с помощью указателя на объект базового класса
ООП
Виртуальные функции
На первый взгляд, переопределение виртуальной функции в производном классе
ООП
Виртуальные функции
На первый взгляд, переопределение виртуальной функции в производном классе
Наиболее важное отличие заключается в том, что прототип переопределяемой виртуальной функции должен точно совпадать с прототипом, определенным в базовом классе.
Этим виртуальные функции отличаются от перегруженных, которые отличаются типами и количеством параметров. (Фактически при перегрузке функций типы и количество их параметров должны отличаться! Именно эти отличия позволяют компилятору выбирать правильный вариант перегруженной функции.)
При переопределении виртуальной функции все аспекты их прототипов должны быть одинаковыми. Если не соблюдать это правило, компилятор будет считать эти функции просто перегруженными, а их виртуальная природа будет потеряна.
Второе важное ограничение заключается в том, что виртуальные функции не могут быть статическими членами классов. Кроме того, они не могут быть дружественными функциями. И, наконец, конструкторы не могут быть виртуальными, хотя на деструкторы это ограничение не распространяется.
Из-за перечисленных ограничений для переопределения виртуальной функции в производном классе используется термин замещение (overriding).
ООП
В языке С++ для поддержки объектно-ориентированного программи-рования используется динамическая идентификация
ООП
В языке С++ для поддержки объектно-ориентированного программи-рования используется динамическая идентификация
Система RTTI позволяет идентифицировать тип объекта при выполнении программы. Дополнительные операторы приведения типов обеспечивают более безопасный способ приведения. Поскольку один из этих операторов приведения (оператор dynamic_cast) непосредственно связан с системой динамической идентификации типов имеет смысл рассмотреть их вместе.
Механизм динамической идентификации типа состоит из трех составляющих:
оператора динамического преобразования типа dynamic_cast<>;
оператора идентификации точного типа объекта typeid();
класса type_info.
Динамическая идентификация типов не характерна для таких неполиморфных языков, как язык С. В этих языках пет необходимости определять тип объекта при выполнении программы, поскольку тип каждого объекта известен еще на этапе компиляции. Однако в полиморфных языках, таких как С++, возникают ситуации, в которых тип объекта на этапе компиляции неизвестен, поскольку природа этого объекта уточняется только в ходе выполнения программы. Язык С++ реализует полиморфизм с помощью иерархий классов, виртуальных функций и указателей на объекты базового класса. Поскольку указатели на объекты базового класса могут ссылаться и на объекты производных классов, не всегда можно предсказать, на объект какого типа они будут ссылаться в тот или иной момент. Эту идентификацию приходится осуществлять в ходе выполнения программы.
Динамическая идентификация типа
ООП
Для идентификации типа объекта используется оператор typeid, определенный в заголовке
ООП
Для идентификации типа объекта используется оператор typeid, определенный в заголовке
Его формат: typeid (object)
Здесь, параметр object является объектом, тип которого мы хотим идентифицировать. Он может иметь любой тип, в том числе встроенный или определенный пользователем.
Оператор typeid возвращает ссылку на объект типа type_infо, описывающий тип объекта. Стандарт "ISO/IEC 14882:2003(Е), Programming languages - С++" определяет класс type_infо так:
Динамическая идентификация типа
class type_info
{
public:
virtual ?type_info();
bool operator == (const type_info& rhs) const;
bool operator != (const type_info& rhs) const;
bool before(const type_info& rhs) const;
const char* name() const;
private:
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
Как видно, объекты типа type_info невозможно ни создать, ни скопировать – конструкторы и операция присваивания закрыты.
Объекты типа type_info можно сравнивать между собой, используя перегружен-ные операторы "==" и "!=", – и это основные операции, которые используются сов-местно с оператором typeid(); функция name() возвращает указатель на имя заданного типа.
Метод before() позволяет сортировать информацию о типе type_info. Нет никакой связи между отношениями упорядочения, определяемыми before(), и отношениями наследования. Если вызывающий объект предшествует объекту, использованному как параметр, функция before() возвращает значение true.
ООП
#include
#include
using namespace std;
class Mammal {public: //класс Mammal –
ООП
#include
#include
using namespace std;
class Mammal {public: //класс Mammal –
virtual bool lays_eggs() { return false; }
// ... };
class Cat: public Mammal { public: /* ... */ };
class Platypus: public Mammal { public:
bool lays_eggs() { return true; } /* ... */ };
int main()
{ Mammal *p, AnyMammal;
Cat cat;
Platypus platypus;
p = &AnyMammal;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
p = &cat;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
p = &platypus;
cout << "Указатель p ссылается на объект типа ";
cout << typeid(*p).name() << endl;
return 0;
}
Динамическая идентификация типа
Самое важное свойство оператора typeid проявляется, когда он применяется к указателю на объект базового класса. В этом случае он автоматически возвращает тип реального объекта, на который ссылается указатель, причем этот объект может быть экземпляром как базового, так и производного классов. (Напомним, что указатель на объект базового класса может ссылаться на объекты производного класса.)
Т.о, в ходе выполнения программы, используя оператор typeid, можно идентифицировать тип объекта, на который ссылается указатель базового класса.
Результаты работы этой программы приведены ниже.
Указатель р ссылается на объект типа class Mammal,
Указатель р ссылается на объект типа class Cat,
Указатель р ссылается на объект типа class Platypus.
Пример применения typeid к иерархии полиморфных классов
ООП
Динамическая идентификация типа
Если оператор typeid применяется к указателю на объект полиморфного
ООП
Динамическая идентификация типа
Если оператор typeid применяется к указателю на объект полиморфного
В любом случае, если оператор typeid применяется к указателю на объект неполиморфного базового класса, возвращается базовый тип указателя. Иначе говоря, невозможно определить, на объект какого типа ссылается этот указатель на самом деле.
Закомментируем, например, ключевое слово virtual, стоящее перед именем функции lays_egg() в классе Mammal, перекомпилируем программу и запустим ее вновь:
Указатель р ссылается на объект типа class Mammal
Указатель р ссылается на объект типа class Mammal
Указатель р ссылается на объект типа class Mammal
Класс Mammal больше не является полиморфным, и каждый объект теперь имеет тип Mammal, т.е. тип указателя р.
Поскольку оператор typeid обычно применяется к разыменованным указателям, следует предусмотреть обработку исключительной ситуации bad_typeid, которая может возникнуть, если указатель будет нулевым.
Оператор typeid имеет вторую форму, получающую в качестве аргумента имя типа.
Ее общий вид приведен ниже:
typeid(<имя_типа>)
Например, следующий оператор совершенно правилен: cout << typeid(int).name();
Данная форма оператора typeid позволяет получить объект класса type_info, описывающий заданный тип. Благодаря этому ее можно применять в операторах сравнения типов.
Например: void WhatMammal(Mammal &ob)
{ cout << "Параметр ob ссылается на объект типа ";
cout << typeid(ob).name() << endl;
if(typeid(ob) == typeid(Cat))
cout << "Кошки боятся воды.\n";
}
Разбор примера применения typeid к иерархии полиморфных классов
ООП
#include
using namespace std;
class Mammal { public:
virtual bool lays_eggs()
ООП
#include
using namespace std;
class Mammal { public:
virtual bool lays_eggs()
// ... };
class Cat: public Mammal { public: /* … /* };
class Platypus: public Mammal { public:
bool lays_eggs() { return true; } /* ... */ };
class Dog: public Mammal { public: /* … /* };
// Фабрика объектов, производных от класса Mammal
Mammal *factory()
{ switch(rand() % 3 ) { case 0: return new Dog;
сase 1: return new Cat; case 2: return new Platypus; }
return 0; }
int main()
{ Mammal *ptr; // Указатель на базовый класс
int i; int c=0, d=0, p=0;
// Создаем и подсчитываем объекты
for(i=0; i<10; i++) // Создаем и подсчитываем объекты
{ ptr = factory(); // Создаем объект
cout<<"Тип объекта: "<< typeid(*ptr).name();
cout<< endl;
// Подсчёт
if(typeid(*ptr) == typeid(Dog)) d++;
if(typeid(*ptr) == typeid(Cat)) c++;
if(typeid(*ptr) == typeid(Platypus)) p++; }
cout << endl;
cout << " Созданные животные:\n";
cout << " Собаки: " << d << endl;
cout << " Кошки: " << c << endl;
cout << " Утконосы: " << p << endl;
return 0;
}
Динамическая идентификация типа
Функция factory() создает объекты различных классов, производных от класса Mammal. (Функции, создающие объекты, иногда называются фабриками объектов (object factory)). Конкретный тип создаваемого объекта задается функцией rand(), которая представляет собой генератор случайных чисел в языке С++. Т.е., тип создаваемого объекта заранее не известен. Программа создаст 10 объектов и подсчитывает количество объектов каждого типа. Поскольку все объекты генерируются функцией factory(), для идентификации фактического типа объекта используется оператор typeid.
Результаты работы этой программы приведены ниже.
Тип объекта: class Platypus
Тип объекта: class Platypus
Тип объекта: class Cat
Тип объекта: class Cat
Тип объекта: class Platypus
Тип объекта: class Cat
Тип объекта: class Dog
Тип объекта: class Dos
Тип объекта: class Cat
Тип объекта: class Platypus
Созданные животные:
Собаки: 2
Кошки: 4
Утконосы: 4
Применение динамической идентификации типа
ООП
#include
using namespace std;
template class myclass {
T
ООП
#include
using namespace std;
template
T
public:
myclass(T i) { a = i; } // ...
};
int main()
{ myclass
myclass
cout << "Тип объекта o1: ";
cout << typeid(o1).name() << endl;
cout << "Тип объекта o2: ";
cout << typeid(o2).name() << endl;
cout << "Тип объекта o3: ";
cout << typeid(o3).name() << endl;
cout << endl;
if(typeid(o1) == typeid(o2))
cout << "Объекты o1 и o2 имеют одинаковый тип\n";
if(typeid(o1) == typeid(o3))
cout << "Ошибка\n";
else
cout << "Объекты o1 и o3 имеют разные типы\n";
return 0;
}
Динамическая идентификация типа
Тип объекта, являющегося объектом шаблонного класса, определяется, в частности, тем, какие данные используются в качестве обобщенных при создании конкретного объекта. Если при создании двух экземпляров шаблонного класса используются данные разных типов, считается, что эти объекты имеют разные типы. Применение оператора typeid к шаблонным классам Динамическая идентификация типов применяется не во всех программах. Однако при работе с полиморфными типами механизм RTTI позволяет распознавать типы объектов в любых ситуациях.
Результаты работы этой программы приведены ниже.
Тип объекта o1: class myclass
Тип объекта o2: class myclass
Тип объекта o3: class myclass
Объекты ol и о2 имеют одинаковый типы Объекты ol и оЗ имеют разные типы
Как видим, хотя два объекта представляют собой экземпляры одного и того же шаблонного класса, если параметры не совпадают, их типы считаются разными. В данной программе объект o1 имеет тип myclass
ООП
В языке С++ существуют пять операторов приведения типов. Первый оператор
ООП
В языке С++ существуют пять операторов приведения типов. Первый оператор
Операция приведения типов в стиле С:
Она может записываться в двух формах:
(тип) выражение – для C и С++
тип (выражение) – только для С++
Результатом операции является значение заданного типа, например:
int а = 2;
float b = 6.8;
printf ("%lf %d", double (a), (int) b);
Величина а преобразуется к типу double, а переменная b – к типу int с отсечением дробной части, в обоих случаях внутренняя форма представления результата операции преобразования иная, чем форма исходного значения.
Остальные четыре оператора приведения типов были добавлены впоследствии, в С++.
К ним относятся операторы: dynamic_cast,
const_cast,
reinterpret_cast,
static_cast.
Эти операторы позволяют полнее контролировать процессы приведения типов.
Операторы приведения типа
ООП
Оператор dynamic_cast
Операторы приведения типа
Оператор dynamic_cast осуществляет динамическое приведение типа с
ООП
Оператор dynamic_cast
Операторы приведения типа
Оператор dynamic_cast осуществляет динамическое приведение типа с
Общий вид оператора dynamic_cast:
dynamic_cast
Здесь - параметр target_type задает результирующий тип,
- параметр ехрr – выражение, которое приводится к новому типу.
Результирующий тип должен быть указательным или ссылочным, а приводимое выражение – вычислять указатель или ссылку. Таким образом, оператор dynamic_cast можно применять для приведения типов указателей или ссылок.
Оператор dynamic_cast предназначен для приведения полиморфных типов. Dynamic_cast применяется для приведения типов в иерархической структуре наследования; он может приводить базовый тип к производному, производный к базовому или один производный тип – к другому производному типу. Допустим, даны два полиморфных класса B и D, причем класс D является производным от класса B. Тогда оператор dynamic_cast может привести указатель типа D* к типу B*. Это возможно благодаря тому, что указатель на объект базового класса может ссылаться на объект производного класса. Однако обратное динамическое приведение указателя типа B* к типу D* возможно лишь в том случае, если указатель действительно ссылается на объект класса D.
Оператор dynamic_cast достигает цели, если указатель или ссылка, подлежавшие приведению, ссылаются на объект результирующего класса или объект класса, производного от результирующего. В противном случае приведение типов считается неудавшимся. В случае неудачи оператор dynamic_cast, примененный к указателям, возвращает нулевой указатель. Если оператор dynamic_cast применяется к ссылкам, в случае ошибки генерируется исключительная ситуация bad_cast.
ООП
Оператор dynamic_cast
Операторы приведения типа
Пример.
Класс Base является полиморфным, a Derived
ООП
Оператор dynamic_cast
Операторы приведения типа
Пример.
Класс Base является полиморфным, a Derived
Base *bp, b_ob;
Derived *dp, d_ob;
bp = &d_ob; // указатель базового типа ссылается на объект производного класса
dp=dynamic_cast
if(dp) cout << "Приведение выполнено успешно";
Приведение указателя bр, имеющего базовый тип, к указателю dp, имеющему производный тип, выполняется успешно, поскольку указатель bp на самом деле ссылается на объект класса Derived. Т.о, этот фрагмент выводит на экран сообщение "Приведение выполнено успешно".
Однако в следующем фрагменте приведение не выполняется, потому что указатель bр ссылается на объект класса Base, а приведение базового объекта к производному невозможно:
bp = &b_ob; // указатель базового типа ссылается на объект класса Base
dp = dynamic_cast
if(!dp) cout << "Приведение не выполняется";
Поскольку приведение невозможно, фрагмент выводит на экран сообщение "Приведение не выполняется".
ООП
Применение dynamic_cast к шаблонным классам
Операторы приведения типа
#include
using namespace std;
template
ООП
Применение dynamic_cast к шаблонным классам
Операторы приведения типа
#include
using namespace std;
template
public: Num(T x) { val = x; }
virtual T getval() { return val; } /* ... */ };
template
{ public: SqrNum(T x) : Num
T getval() { return val * val; } };
int main()
{ Num
SqrNum
Num
bp = dynamic_cast
if(bp) { cout << "Приведение типа SqrNum
типу Num
cout<<"Значение равно "<
dp = dynamic_cast
if(dp) cout << "Ошибка\n"; else { cout<<"Приве-дение типа Num
cout<<"Приведение указателя на объект базового \n"; cout<<" класса к указателю на объект производного типа невозможно\n"; } cout << endl;
bp = dynamic_cast
if(bp) cout << "Ошибка\n"; else cout<<"Приведе-ние типа Num
return 0;
}
Результаты работы этой программы:
Приведение типа SqrNum
Значение равно 9
Приведение типа Num
Приведение указателя на объект базового класса к указателю на объект производного класса невозможно.
Приведение типа Num
Это два разных типа.
Основной смысл этого фрагмента заключается в том, что с помощью оператора dynamic_cast нельзя привести указатель на объект одной шаблонной специализации к указателю на экземпляр другой шаблонной специализации.
Напоминание: точный тип объекта шаблонного класса определяется типом данных, которые используются при его создании. Таким образом, типы Num
ООП
Оператор const_cast
Операторы приведения типа
Оператор const_cast используется для явного замещения модификаторов
ООП
Оператор const_cast
Операторы приведения типа
Оператор const_cast используется для явного замещения модификаторов
Общий вид оператора const_cast:
const_cast
Здесь - параметр type задает результирующий тип приведения,
- параметр ехрr – выражение, которое приводится к новому типу.
#include Эта программа выводит на экран следующие результаты:
using namespace std;
void sqrval(const int *val) { int *p;
// Удаление модификатора const
p = const_cast
*p = *val * *val; }
int main()
{ int x = 10;
cout<<"Значение x перед вызовом: "<
cout<<"Значение х после вызова: "<
}
Значение х перед вызовом: 10
Значение х после вызова: 100
Как видим, функция sqrval() изменяет значение переменной х, даже если ее параметр является константным указателем.
Применение оператора const_cast для удаления атрибута const небезопасно. Его следует использовать осторожно.
Атрибут const можно удалить только с помощью оператора const_cast.
Операторы dynamic_cast, static_cast и reinterpret_cast на атрибут const не влияют.
ООП
Оператор static_cast
Операторы приведения типа
Оператор static_cast выполняет неполиморфное приведение. Его можно
ООП
Оператор static_cast
Операторы приведения типа
Оператор static_cast выполняет неполиморфное приведение. Его можно
Оператор static_cast имеет следующий вид:
static_cast
Здесь - параметр type задает результирующий тип приведения,
- параметр ехрr – выражение, которое приводится к новому типу.
Оператор static_cast, по существу, заменяет исходный оператор приведения. Просто он выполняет неполиморфное приведение.
Например, следующая программа приводит переменную типа int к типу double:
#include
using namespace std;
int main()
{
int i;
for(i=0; i<10; i++)
cout << static_cast
return 0;
}