Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

840

Часть V

Объектно-ориентированное

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

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

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

Если базовый и производный классы имеют общий открытый интерфейс, то производный называется подтипом базового. Так, PerspectiveCamera является подтипом класса Camera. В C++ существует специальное отношение между типом и подтипом, позволяющее указателю или ссылке на базовый класс адресовать любой из производных от него подтипов без вмешательства программиста. (Такая возможность

манипулировать несколькими типами с помощью указателя или ссылки на базовый класс называется полиморфизмом.) Если дана функция:

void lookAt( const Camera *pCamera );

то мы реализуем lookAt(), программируя интерфейс базового класса Camera и не заботясь о том, на что указывает pCamera: на объект класса PerspectiveCamera, на объект класса OrthographicCamera или на объект, описывающий еще какой-то вид камеры, который мы пока не определили.

При каждом вызове lookAt() ей передается адрес объекта, принадлежащего к одному из подтипов Camera. Компилятор автоматически преобразует его в указатель на подходящий

//правильно: автоматически преобразуется в Camera* OrthographicCamera ocam;

lookAt( &ocam );

//...

//правильно: автоматически преобразуется в Camera* PerspectiveCamera *pcam = new PerspectiveCamera;

базовый класс: lookAt( pcam );

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

С++ для начинающих

841

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

Нахождение (или разрешение) нужной функции во время выполнения называется динамическим связыванием (dynamic binding) (по умолчанию функции разрешаются статически во время компиляции). В C++ динамическое связывание поддерживается с помощью механизма виртуальных функций класса. Полиморфизм подтипов и динамическое связывание формируют основу объектно-ориентированного программирования, которому посвящены следующие главы.

В главе 17 рассматриваются имеющиеся в C++ средства поддержки объектно-

ориентированного программирования и изучается влияние наследование на такие механизмы, как конструкторы, деструкторы, почленная инициализация и присваивание; для примера разрабатывается иерархия классов Query, поддерживающая систему текстового поиска, введенную в главе 6.

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

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

Глава 20 посвящена углубленному рассмотрению библиотеки потокового ввода/вывода iostream. Эта библиотека представляет собой иерархию классов, поддерживающую как виртуальное, так и множественное наследование.

17

17. Наследование и подтипизация классов

В главе 6 для иллюстрации обсуждения абстрактных контейнерных типов мы

частично реализовали систему текстового поиска и инкапсулировали ее в класс TextQuery. Однако мы не написали к ней никакой вызывающей программы,

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

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

Программа для запуска нашей системы текстового поиска будет выглядеть следующим образом:

С++ для начинающих

842

#include "TextQuery.h"

int main()

{

TextQuery tq;

tq.build_up_text(); tq.query_text();

}

build_text_map() это слегка видоизмененная функция-член doit() из главы 6. Ее основная задача построить отображение для хранения позиций всех значимых слов текста. (Если помните, мы не храним семантически нейтральные слова типа союзов if, and, but и т.д. Кроме того, мы заменяем заглавные буквы на строчные и устраняем суффиксы, обозначающие множественное число: например, testifies преобразуется в testify, а marches в march.) С каждым словом ассоциируется вектор позиций, в котором хранятся номера строки и колонки каждого вхождения слова в текст.

query_text() принимает запросы пользователя и преобразует их во внутреннюю форму на основе иерархии классов Query с одиночным наследованием и динамическим связыванием. Внутреннее представление запроса применяется к отображению слов на вектор позиций, построенному в build_text_map(). Ответом на запрос будет множество строк текстового файла, удовлетворяющих заданному критерию:

Enter a query - please separate each item by a space.

Terminate query (or session) with a dot( . ).

==> fiery && ( bird || shyly )

fiery ( 1 ) lines match bird ( 1 ) lines match shyly ( 1 ) lines match

( bird || shyly ) ( 2 ) lines match

fiery && ( bird || shyly ) ( 1 ) lines match Requested query: fiery && ( bird || shyly )

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her.

Внашей системе мы выбрали следующий язык запросов:

одиночное слово, например Alice или untamed. Выводятся все строки, в которых оно встречается, причем каждой строке предшествует ее номер, заключенный в скобки. (Строки печатаются в порядке возрастания номеров). Например:

==> daddy

daddy ( 3 ) lines match

Requested query: daddy

( 1 ) Alice Emma has long flowing red hair. Her Daddy says

( 4 ) magical but untamed. "Daddy, shush, there is no such thing," ( 6 ) Shyly, she asks, "I mean, Daddy, is there?"

С++ для начинающих

843

∙ запрос НЕ”, формулируемый с помощью оператора !. Выводятся все строки, где не встречается указанное слово. Например, так формулируется отрицание запроса 1:

==> ! daddy

daddy ( 3 ) lines match

! daddy ( 3 ) lines match Requested query: ! daddy

( 2 ) when the wind blows through her hair, it looks almost alive,

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her, ( 5 ) she tells him, at the same time wanting him to tell her more.

запрос ИЛИ”, формулируемый с помощью оператора ||. Выводятся все строки, в которых встречается хотя бы одно из двух указанных слов:

==> fiery || untamed

fiery ( 1 ) lines match untamed ( 1 ) lines match

fiery || untamed ( 2 ) lines match Requested query: fiery || untamed

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her, ( 4 ) magical but untamed. "Daddy, shush, there is no such thing,"

запрос И”, формулируемый с помощью оператора &&. Выводятся все строки, где оба указанных слова встречаются, причем располагаются рядом. Сюда входит и случай, когда одно слово является последним в строке, а другое первым в следующей:

==> untamed && Daddy

untamed ( 1 ) lines match daddy ( 3 ) lines match

untamed && daddy ( 1 ) lines match

Requested query: untamed && daddy

( 4 ) magical but untamed. "Daddy, shush, there is no such thing,"

Эти элементы можно комбинировать:

fiery && bird || shyly

Однако обработка производится слева направо, и все элементы имеют одинаковые приоритеты. Поэтому наш составной запрос интерпретируется как fiery bird ИЛИ shyly, а не как fiery bird ИЛИ fiery shyly:

==> fiery && bird || shyly

fiery ( 1 ) lines match bird ( 1 ) lines match

fiery && bird ( 1 ) lines match shyly ( 1 ) lines match

fiery && bird || shyly ( 2 ) lines match