Категории
Самые читаемые книги
ЧитаемОнлайн » Компьютеры и Интернет » Программирование » Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Читать онлайн Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 143 144 145 146 147 148 149 150 151 ... 337
Перейти на страницу:
разделе предметом дискуссии будет не программирование, проектирование и графика, а язык программирования. Разрабатывая нашу библиотеку графического интерфейса, мы использовали три основных механизма.

• Вывод. Это способ построения одного класса из другого так, чтобы новый класс можно было использовать вместо исходного. Например, класс Circle является производным от класса Shape, иначе говоря, класс Circle является разновидностью класса Shape или класс Shape является базовым по отношению к классу Circle. Производный класс (в данном случае Circle) получает все члены базового класса (в данном случае Shape) в дополнение к своим собственным. Это свойство часто называют наследованием (inheritance), потому что производный класс наследует все члены базового класса. Иногда производный класс называют подклассом (subclass), а базовый — суперклассом (superclass).

• Виртуальные функции. В языке С++ можно определить функцию в базовом классе и функцию в производном классе с точно таким же именем и типами аргументов, чтобы при вызове пользователем функции базового класса на самом деле вызывалась функция из производного класса. Например, когда класс Window вызывает функцию draw_lines() из класса Circle, выполняется именно функция draw_lines() из класса Circle, а не функция draw_lines() из класса Shape. Это свойство часто называют динамическим полиморфизмом (run-time polymorphism) или динамической диспетчеризацией (run-time dispatch), потому что вызываемые функции определяются на этапе выполнения программы по типу объекта, из которого они вызываются.

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

Наследование, динамический полиморфизм и инкапсуляция — наиболее распространенные характеристики объектно-ориентированного программирования (object-oriented programming). Таким образом, язык C++ непосредственно поддерживает объектно-ориентированное программирование наряду с другими стилями программирования. Например, в главах 20-21 мы увидим, как язык C++ поддерживает обобщенное программирование. Язык C++ позаимствовал эти ключевые механизмы из языка Simula67, первого языка, непосредственно поддерживавшего объектно-ориентированное программирование (подробно об этом речь пойдет в главе 22).

Довольно много технической терминологии! Но что все это значит? И как на самом деле эти механизмы работают? Давайте сначала нарисуем простую диаграмму наших классов графического интерфейса, показав их отношения наследования.

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

14.3.1. Схема объекта

Как объекты размещаются в памяти? Как было показано в разделе 9.4.1, схема объекта определяется членами класса: данные-члены хранятся в памяти один за другим. Если используется наследование, то данные-члены производного класса просто добавляются после членов базового класса. Рассмотрим пример.

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

 

 Для того чтобы обработать вызов виртуальной функции, нам нужна еще одна порция данных в объекте класса Shape: информация о том, какая функция будет на самом деле вызываться при обращении к функции draw_lines() из класса Shape. Для этого обычно в таблицу функций заносится ее адрес. Эта таблица обычно называется vtbl (таблица виртуальных функций), а ее адрес часто имеет имя vptr (виртуальный указатель). Указатели обсуждаются в главах 17-18; здесь они действуют как ссылки. В конкретных реализациях языка таблица виртуальных функций и виртуальный показатель могут называться иначе. Добавив таблицу vptr и указатели vtbl к нашему рисунку, получим следующую диаграмму.

Поскольку функция draw_lines() — первая виртуальная функция, она занимает первую ячейку в таблице vtbl, за ней следует функция move(), вторая виртуальная функция. Класс может иметь сколько угодно виртуальных функций; его таблица vtbl может быть сколь угодно большой (по одной ячейке на каждую виртуальную функцию). Теперь, когда мы вызовем функцию x.draw_lines(), компилятор сгенерирует вызов функции, найденной в ячейке draw_lines() таблицы vtbl, соответствующей объекту x. В принципе код просто следует по стрелкам на диаграмме.

Итак, если объект x относится к классу Circle, будет вызвана функция Circle::draw_lines(). Если объект x относится к типу, скажем, Open_polyline, который использует таблицу vtbl точно в том виде, в каком ее определил класс Shape, то будет вызвана функция Shape::draw_lines(). Аналогично, поскольку в классе Circle не определена его собственная функция move(), при вызове x.move() будет выполнена функция Shape::move(), если объект x относится к классу Circle. В принципе код, сгенерированный для вызова виртуальной функции, может просто найти указатель vptr и использовать его для поиска соответствующей таблицы vtbl и вызова нужной функции оттуда. Для этого понадобятся два обращения к памяти и обычный вызов функции, — быстро и просто.

Класс Shape является абстрактным, поэтому мы не можем на самом деле непосредственно создать объект класса Shape, но класс Open_polyline имеет точно такую же простую структуру, поскольку не добавляет никаких данных-членов и не определяет виртуальную функцию. Таблица виртуальных функций vtbl определяется для каждого класса, в котором определена виртуальная функция, а не для каждого объекта, поэтому таблицы vtbl незначительно увеличивают размер программы.

Обратите внимание на то, что на рисунке мы не изобразили ни одной невиртуальной функции. В этом не было необходимости, поскольку об этих функциях мы не можем сказать что-то особенное и они не увеличивают размеры объектов своего класса. Определение функции, имеющей то же имя и те же типы аргументов, что и виртуальная функция из базового класса (например, Circle::draw_lines()), при котором функция из производного класса записывается в таблицу vtbl вместо соответствующей функции из базового класса, называется замещением (overriding). Например, функция Circle::draw_lines() замещает функцию Shape::draw_lines().

Почему мы говорим о таблицах vtbl и схемах размещения в

1 ... 143 144 145 146 147 148 149 150 151 ... 337
Перейти на страницу:
На этой странице вы можете бесплатно скачать Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп торрент бесплатно.
Комментарии
КОММЕНТАРИИ 👉
Комментарии
Мишель
Мишель 31.01.2025 - 12:20
Книга очень понравилась. Интригующий сюжет 
Аннушка
Аннушка 16.01.2025 - 09:24
Следите за своим здоровьем  книга супер сайт хороший
Татьяна
Татьяна 21.11.2024 - 19:18
Одним словом, Марк Твен!
Без носенко Сергей Михайлович
Без носенко Сергей Михайлович 25.10.2024 - 16:41
Я помню брата моего деда- Без носенко Григория Корнеевича, дядьку Фёдора т тётю Фаню. И много слышал от деда про Загранное, Танцы, Савгу...