Г. Шилтд - Самоучитель C++ (DJVU) (1114955), страница 46
Текст из файла (страница 46)
По существу, виртуальная функция реализует идею "один интерфейс, множество методов", которая лежит в основе полиморфизма. Виртуальная функция внутри базового класса определяет вид интерфейса этой функции. Каждое переопределение виртуальной функции в производном классе определяет ее реализацию, связанную со спецификой производного класса. Таким образом, переопределение создает конкретный метод, При переопределении виртуальной функции в производном классе, ключевое слово иггца1 не требуется. Виртуальная функция может вызываться так же, как и любая другая функция-член. Однако наиболее интересен вызов виртуальной функции через указатель, благодаря чему поддерживается динамический полиморфизм.
Из предыдущего раздела вы знаете, что указатель базового класса можно использовать в качестве указателя на объект производного класса. Если указатель базового класса ссылается на объект производного класса, который содержит виртуальную функцию и для которого виртуальная функция вызывается через этот указатель, то компилятор определяет, какую версию виртуальной функции вызвать, основываясь при этом на типе обьекта, на который ссылается указатель. При этом определение конкретной версии виртуальной функции имеет место не в процессе компиляции, а в процессе выполнения программы. Другими словами, тип объекта, на который ссылается указатель, и определяет ту версию виртуальной функции, которая будет выполняться. Поэтому, если два или более различных класса являются производными от базового, содержащего виртуальную функцию, то, если указатель базового класса ссылается на разные объекты этих производных 307 Глава 10, Виртуальные функции классов, выполняются различные версии виртуальной функции.
Этот процесс является реализацией принципа динамического полиморфизма. Фактически, о классе, содержащем виртуальную функцию, говорят как о лолиморфном классе (ро(утотр7пс с!ат). 1. Рассмотрим короткий пример использования виртуальной функции: Простой пример использования виртуальной функции ()1пс1пбв <1ов~теип> пя(по паиеярасе всб; с1аяя Ьаяе рп?~11с: 1пс 1; Ьаяв (ьпсх) ( 1 = хг ) чфгбпа1 чо(б Екпс () ( сопя « "Выполнение функции Гцпс() базового класса: соиЬ « 1 « '1п'; с1аяя бвк1~твб1: риЫ1с Ьаяе рцЬ11с: бег1чеб1(1пс х): Ьаяе(х) ( ) уофбйипс () ( сопя « "Выполнение функции ~ипс() класса бегфуеб1: соцб « 1 * 1 « '~п'; с1аяя бегфуеб2: рпЫфс Ьаяе ( рпЫфс: бвгЫвб2 (1псх): Ьавв(х) ( ) лофб й ппс ( ) ( сепг « "Выполнение функции йцпс ( ) класса бвг1теб2: соп' « 1 + 1 « '1п'( фпб льафп() Ьаяе *р; Само учитель С++ 308 Ьаве оЬ (10) т с(ег~лес(1 с( оЪ1(10) ) бегггеб2 о оЬ2(10); р =- аоЬ; р->гцгс ( ) ~ у/ функция г цпс ( ) классаЬаве р = ас( оЬ1; р->Ецпс (); ll функция гцпс () производного класса с(егЫео1 р = ас( ОЪ2; р->Кцпс(]т // функция свпо ( ) производного класса аегггес)2 гееагп 0; После выполнения программы на экране появится следующее: выполнение функции ~ало ( ) базового класса: 10 выполнение функции 1 цпо ( ) класса с(егъгес(1: 100 Выполнение функции ~цло() класса бегъгес(2: 20 Переопределение виртуальной функции внутри производного класса может показаться похожим на перегрузку функций.
Однако эти два процесса совершенно различны. Во-первых, перегружаемая функция должна отличаться типом и/или числом параметров, а переопределяемая виртуальная функция должна иметь точно такой же тип параметров, то же их число, и такой же тип возвращаемого значения. (На самом деле, если при переопределении виртуальной функции вы изменяете число или тип параметров, она просто становится перегружаемой функцией и ее виртуальная природа теряется.) Далее, виртуальная функция должна быть членом класса. Это не относится к перегружаемым функциям. Кроме этого, если деструкторы могут быть виртуальными, то конструкторы нет. Чтобы подчеркнуть разницу между перегружаемыми функциями и переопределяемыми виртуальными функциями, для описания переопределения виртуальной функции используется термин подмена (огетйнф.
В рассмотренном примере создается три класса. В классе Ьаве определяется виртуальная функция Гипс(). Затем этот класс наследуется двумя производными классами: дег!саед! и деп~ед2, Каждый из этих классов переопределяет функцию Гаас() по-своему.
Внутри функции гпа1п() указатель базового класса р поочередно ссылается на объекты типа Ьаве, депуед1 и дептед2. Первым указателю р присваивается адрес объекта оЬ (объекта типа Ьаве). При вызове функции Гапсо через указатель р используется ее версия из класса Ьаве. Следующим указателю р присваивается адрес объекта д оЫ и функция Гвпс() вызывается снова.
Поскольку версия вызываемой виртуальной функции определяется типом объекта, на который ссылается указатель, то вызывается та версия функции, которая переопределяется в классе депуед1. Наконец, указателю р присваивается адрес объекта д оЬ2, и снова вызывается функция Гавел. При этом выполняется та версия функции Гапс(), которая определена внутри класса деггуед2, функции Виртуальные Глава 'т().
Ключевым для понимания предьшутцего примера является тот факт, что, вопервых, тип адресуемого через указатель объекта определяет вызов той или иной версии подменяемой виртуальной функции, во-вторых, выбор конкретной версии происходит уже в процессе выполнения программы. 2. Виртуальные функции имеют иерархический порядок наследования. Кроме того, если виртуальная функция не подменяется в производном классе, то используется версия функции, определенная в базовом классе.
Например, ниже приведена слегка измененная версия предыдущей программы: // Иерархический порядок виртуальных функций ((1пс1иое <1овсгеап~> ця)пб патпеярасе всс(; о!аяя Ьаяе ( рцЬ11с: 1пС хт Ьаве (апс х) ( ( = х; ч(г1иа( чоЫ Гипс() ( сои1 « "Выполнение функции аппо() оазового класса: соус « ( « 'Хп'; с1аяя с(егучес)1: рпЬ1)с Ьаяе риЬ11с: <)егзлтес)1 (1пкх): Ьаве(х) ( ) чоЫ гипс ( ) ( соцс « "Вьктолнение функции ~ипс() класса с(егучес)1: сонг « 1 " ) « '1п'т ) т с1аяя с(ег)чег)2: рпЬ11с Ьаяе ( рпЬ11с: с(егг-тес)2(1пс х): Ьаве(х) ( ) // в классе г(ег)чеа2 функция гппс ( ) не подменяется гп таа1п ( ) Ьаяе *р; Ьаяе оЬ(10); с(ег1чей1 6 оЬ1(10), бег(чес(2 б оЬ2(10); р = яоЬ; р->Гипс()т // функция аппо ( ) базового класса 370 Самоучитель С++ р = аи оЬ17 р->Екпс(); !7 Функция Гцпс() производного класса беггуег(1 р -- ье( оЬ2у р->гипс(); l! Функция гцпс() базового класса гебзгп 0; После выполнения программы на экран выводится следующее: Выполнение функции Еипс () базового класса: 10 Выполнение функции г цпс ( ) класса «(ег1'гее(1: 100 Выполнение функции липс () базового класса: 10 В этой программе в классе дег1уе(32 функция й)псо не подменяется.
Когда указателю р присваивается адрес объекта (1 оЬ2 и вызывается функция ГппеО, используется версия функции из класса Ьам, поскольку она следующая в иерархии классов. Обычно, если виртуальная функция не переопределена в производном классе, используется ее версия из базового класса. 3. В следующем примере показано, как случайные события во время работы программы влияют на вызываемую версию виртуальной функции. Программа выбирает между объектами (1 оЫ и (1 оЬ2 на основе значений, возвращаемых стандартным генератором случайных чисел гавдос. Запомните, выбор конкретной версии функции (цпс() происходит во время работы программы.
(Действительно, при компиляции этот выбор сделать невозможно, поскольку он основан на значениях, которые можно получить только во время работы программы.) /* В этом примере показана работа виртуальной функции при наличии случайных событий во время выполнения программы. Ъ' ((ьпс1пг)е сгояггеазп> (11пс1цг)е <сяЫ1) Ь> цягпд палеярасе яге(; с1аяя Ьаяе ( рвЬ11с: гпбг 7 Ьаяе (зпгх) ( 1 х) ) уггяца1 уоЫ Гипс О ( соаг <с "вьлтолнение функции Галс () базового класса: "7 сонг « 1 « 'хп'; с1аяя Йеггуег)1: рцЬ11с Ьаяе ( рцЬ11с: с(егьлег(1 (гол х) ". Ьаяе(х) ( Глава 10.
Виртуальные функции уоЫ Гипс() соиС « биьлолнение функции тппс () класса <)его<)1: "р соне « 1 * 1 <с '~п'; с1авв с)ег1чео2: рцЬИс Ьазе рвЬ11с: с(егъчес(2(1пв х): Ьаве(х) уоМ гипс ( ) соШ « "Выполнение функции гппс() класса г)егЮео2: соог «1+1« 1пс лв1п () ( Ьаве *р( с(егтчеб1 <1 оЬ1(10); с(егЫе62 <) оЬ2(10); (пс Хог(1-От 1<10г 1+Н = кап<1(); 1Ц(3%2) ) р = ьс( ОЬ1' если число нечетное использовать объект <) оЫ если число четное использовать объект <( оЬ2 вызов подкодяцей версии функции е1зе р = ад оЬ2; р->топо(); гегцгп 0; // Использование виртуальной функции для определения интерфейса ()1пс1цйе <1оввгеата> цз1пя патпезрасе з(с(; 4.