Главная » Статьи » Мои статьи » С++ для начинающих |
Формальный параметр функции ia
может быть ссылкой на IntArray, IntArrayRC
или на IntSortedArray.
Функция-член size()
не является виртуальной и разрешается на этапе компиляции. А вот виртуальный
оператор взятия индекса не может быть разрешен на данном этапе, поскольку
реальный тип объекта, на который ссылается ia,
в этот момент неизвестен. (В главе 17 мы будем говорить о виртуальных функциях более
подробно. Там мы рассмотрим также и накладные расходы, которые влечет за собой
их использование.) Вот как выглядит определение производного класса IntArrayRC:
#endif Этот текст мы поместим в заголовочный файл IntArrayRC.h. Обратите внимание на то, что в наш файл включен заголовочный файл IntArray.h. В классе IntArrayRC мы должны реализовать только те
особенности, которые отличают его от IntArray:
класс IntArrayRC
должен иметь свою собственную реализацию операции взятия индекса; функцию для
проверки индекса и собственный набор конструкторов. Все данные и функции-члены класса IntArray
можно использовать в классе IntArrayRC так, как будто это его собственные
члены. В этом и заключается смысл наследования. Синтаксически наследование
выражается строкой class IntArrayRC : public IntArray Эта строка показывает, что класс IntArrayRC произведен от класса IntArray, другими словами, наследует ему. Ключевое слово public в данном контексте говорит о том, что производный класс сохраняет открытый интерфейс базового класса, то есть что все открытые функции базового класса остаются открытыми и в производном. Объект типа IntArrayRC может использоваться вместо объекта типа IntArray, как, например, в приведенном выше примере с функцией swap(). Таким образом, подкласс IntArrayRC – это расширенная версия класса IntArray. Вот как выглядит реализация операции взятия индекса:
} А вот реализация встроенной функции check_range():
} (Мы говорили о макросе assert()
в разделе 1.3.) Почему проверка индекса вынесена в отдельную функцию, а не
выполняется прямо в теле оператора взятия индекса? Потому что, если мы
когда-нибудь потом захотим изменить что-то в реализации проверки, например
написать свою обработку ошибок, а не использовать assert(),
это будет сделать проще. В каком порядке активизируются конструкторы при создании
производного класса? Первым вызывается конструктор базового класса,
инициализирующий те члены, которые входят в базовый класс. Затем начинает
работать конструктор производного класса, где мы должны проинициализировать
только те члены, которые являются специфичными для подкласса, то есть
отсутствуют в базовом классе. Однако заметим, что в нашем производном классе IntArrayRC
нет новых членов, представляющих данные. Значит ли это, что нам не нужно
реализовывать конструкторы для него? Ведь вся работа по инициализации членов
данных уже проделана конструкторами базового класса. На самом деле конструкторы, как и деструкторы или операторы
присваивания, не наследуются – это правило языка С++. Кроме того,
конструктор производного класса обеспечивает механизм передачи параметров
конструктору базового класса. Рассмотрим пример. Пусть мы хотим создать объект
класса IntArrayRC
следующим образом:
IntArrayRC iarc(ia,8); Нам нужно передать параметры ia и 8 конструктору базового класса IntArray. Для этого служит специальная синтаксическая конструкция. Вот как выглядят реализации двух конструкторов IntArrayRC:
: IntArray( iar, sz ) {} (Мы будем подробно говорить о конструкторах в главах 14 и 17. Там же мы покажем, почему не нужно реализовывать конструктор копирования для IntArrayRC.) Часть определения, следующая за двоеточием, называется списком инициализации членов. Именно
здесь, указав конструктор базового класса, мы можем передать ему параметры.
Тела обоих конструкторов пусты, поскольку их работа состоит исключительно в
передаче параметров конструктору базового класса. Нам не нужно реализовывать
деструктор для IntArrayRC, так как ему просто нечего делать.
Точно так же, как при создании объекта производного типа вызывается сначала
конструктор базового типа, а затем производного, при уничтожении автоматически
вызываются деструкторы – естественно, в обратном порядке: сначала
деструктор производного, затем базового. Таким образом, деструктор базового
класса будет вызван для объекта типа IntArrayRC,
хотя тот и не имеет собственной аналогичной функции. Мы поместим все встроенные функции класса IntArrayRC
в тот же заголовочный файл IntArrayRC.h. Поскольку у нас нет невстроенных
функций, то создавать файл IntArrayRC.C не нужно. Вот пример простой программы, использующей классы IntArray и IntArrayRC:
} При выполнении программа выдаст следующий результат: swap() with IntArray ia1 swap() with IntArrayRC ia2 Assertion failed: ix >= 0 && ix
< _size, file IntArrayRC.h, line 19 Упражнение 2.8 Отношение наследования между типом и подтипом служит
примером отношения является. Так,
массив IntArrayRC
является подвидом массива IntArray, книга является подвидом выдаваемых
библиотекой предметов, аудиокнига является подвидом книги и т.д. Какие из
следующих утверждений верны?
(i) читатель является подвидом библиотеки Упражнение 2.9 Определите, какие из следующих функций могут различаться в
реализации для производных классов и, таким образом, выступают кандидатами в
виртуальные функции:
(h) is_on_loan(); // книга выдана Упражнение 2.10 Ходят споры о том, не нарушает ли принципа инкапсуляции
введение защищенного уровня доступа. Есть мнение, что для соблюдения этого
принципа следует отказаться от использования такого уровня и работать только с
закрытыми членами. Противоположная точка зрения гласит, что без защищенных
членов производные классы невозможно реализовывать достаточно эффективно и в
конце концов пришлось бы везде задействовать открытый уровень доступа.
А каково ваше мнение по этому поводу? Упражнение 2.11 Еще одним спорным аспектом является необходимость явно
указывать виртуальность функций в базовом классе. Есть мнение, что все функции
должны быть виртуальными по умолчанию, тогда ошибка в разработке базового
класса не повлечет таких серьезных последствий в разработке производного, когда
из-за невозможности изменить реализацию функции, ошибочно не определенной в
базовом классе как виртуальная, приходится сильно усложнять реализацию. С
другой стороны, виртуальные функции невозможно объявить как встроенные, и
использование только таких функций сильно снизит эффективность. Каково ваше
мнение? Упражнение 2.12 Каждая из приведенных ниже абстракций определяет целое
семейство подвидов, как, например, абстракция "транспортное средство” может
определять "самолет”, "автомобиль”, "велосипед”. Выберите одно из семейств и
составьте для него иерархию подвидов. Приведите пример открытого интерфейса для
этой иерархии, включая конструкторы. Определите виртуальные функции. Напишите
псевдокод маленькой программы, использующей данный интерфейс.
(f) Курс_продажи | ||||||||||
Просмотров: 841 | | |
Всего комментариев: 0 | |