Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_С++_последная.doc
Скачиваний:
42
Добавлен:
07.05.2019
Размер:
876.54 Кб
Скачать

9.1.5. Виртуальные функции

Связывание – это сопоставление вызова функции с телом. Для обычных методов связывание выполняется на этапе трансляции до запуска программы. Такое связывание называют «ранним», или статическим. При наследовании обычного метода его поведение не меняется в наследнике. Однако бывает необходимо, чтобы поведение некоторых методов базового класса и классов-наследников отличались. Чтобы добиться разного поведения в зависимости от типа, необходимо объявить функцию-метод виртуальной; в C++ это делается с помощью ключевого слова virtual. Виртуальные функции в совокупности с принципом подстановки обеспечивают механизм «позднего» (отложенного) или динамического связывания, которое работает во время выполнения программы. Далее представлен текст программы, использующий виртуальные методы.

class Base // базовый класс

{ public:

virtual void print() const // базовый метод

{ cout << "Base!" << endl; }

class Derive: public Base // производный класс

{ public:

virtual void print() const // переопределенный метод

{ cout << "Derive!" << endl; }

};

void F(Base &d)

{ d.print(); } // предполагается вызов метода базового класса

Base W; // объект базового класса

F(W); // работает метод базового класса

Derive U; // объект производного класса

F(U); // работает метод производного класса

Base *c1 = &W; // адрес объекта базового класса

cl->print(); // вызов базового метода

cl = &U; // адрес объекта производного типа вместо базового

cl->print(): // вызывается производный метод

Класс, в котором определены виртуальные функции (хотя бы одна), называется полиморфным классом.

Ключевое слово virtual можно писать только в базовом классе – это достаточно сделать в объявлении функции. Даже если определение писать без слова virtual, функция все равно будет считаться виртуальной. Правила описания и использования виртуальных функций-методов следующие.

1. Виртуальная функция может быть только методом класса.

2. Любую перегружаемую операцию-метод класса можно сделать виртуальной, например операцию присваивания или операцию преобразования типа.

3. Виртуальная функция наследуется.

4. Виртуальная функция может быть константной.

5. Если в базовом классе определена виртуальная функция, то метод производного класса с таким же именем и прототипом (включая и тип возвращаемого значения, и константность метода) автоматически является виртуальным (слово virtual указывать необязательно) и замещает функцию-метод базового класса.

6. Статические методы не могут быть виртуальными.

7. Конструкторы не могут быть виртуальными.

8. Деструкторы могут (чаще – должны) быть виртуальными – это гарантирует корректное освобождение памяти через указатель базового класса.

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

Виртуальные функции-методы можно перегружать и переопределять (в наследниках) с другим списком аргументов. Если виртуальная функция переопределена с другим списком аргументов, она замещает (скрывает) родительские методы. Константный метод считается отличным от неконстантного метода с таким же прототипом. Текст программы перегрузки и переопределения виртуальных методов.

class Base

{ public: // перегрузка виртуальных методов

virtual int f() const { cout << "Base::f()"<< endl; return 0; }

virtual void f(const string &s) const

{ cout « "Base: :f(string)"« endl: }

};

class Derive: public Base

{ public:

virtual int f(int) const // переопределение виртуальной функции

{ cout << "Derive::f(int)"<< endl; return 0: }

};

Base b. *pb; // объекты базового типа

Derive d. *pd = &d; // объекты производного типа

pb = &d; // здесь нужна виртуальность

pb->f(); // вызывается базовый метод

pb->f("name"); // вызывается базовый метод

pb->f(l); // ошибка!

pd->f(l); // нормально - вызов метода наследника

Через указатель базового класса нельзя вызвать новые виртуальные методы, определенные только в производном классе. Если это необходимо, требуется явное приведение типа:

((Derive *)pb)->f(l);

Родительские методы можно сделать доступными в классе-наследнике при помощи using-объявления.

class Derive: public Base

{ public:

virtual int f(int) const

{ cout << "Derived::f(int)"<< endl: return 0: }

using Base::f; // разрешение использовать скрытые базовые методы

};

Разрешается при переопределении виртуальной функции изменить только тип возвращаемого значения, если это указатель или ссылка.

Виртуальную функцию можно вызвать невиртуально, если указать квалификатор класса:

Base *cl = new Derive(); // адресуется объект производного класса

cl->print(); // вызов метода-наследника

cl->Base::print(); // явно вызывается базовый метод

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

При наличии хотя бы одной виртуальной функции размер класса без полей равен 4 – это размер указателя. Количество виртуальных функций роли не играет – размер класса остается равен 4.

class One

{ virtual void f(void) {}

};

class Two

{ virtual void f(void) {}

virtual void g(void) {}

};

// ...

cout << sizeof(One) << endl; // размер = 4

cout << sizeof(Two) << endl; // размер = 4