Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_OOP.doc
Скачиваний:
4
Добавлен:
01.03.2025
Размер:
4 Mб
Скачать

5.2. Определение виртуальной функции

Виртуальная функция определяется или объявляется в базовом классе со словом virtual, а затем переопределяется в порожденных классах:

class A

{...

public:

virtual void Print( ) {cout << d1 << ’ ‘ << d2 << ’ ‘ << d3;}

...

};

class B: public A

{.....

public:

virtual void Print( ) {A:: Print( ); cout << ’ ‘ << d3 << ’ ‘ << d4;}

...

};

class C: public A

{...

public:

virtual void Print( ) {...};

...

};

Объявив функцию Print( ) виртуальной, в нашем примере мы действительно достигнем гибкости:

  • если введем k = 0, то будет работать pa –> A:: Print( );

  • если введем k = 1, то: pa –> B:: Print( ),

  • если введем k = 2, то: pa –> C:: Print( );

Заметим, что если из класса B (или C) больше нет порождения, то слово virtual в классе B (или C) можно опустить.

Главное свойство виртуальных функций:

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

5.3. Чистая виртуальная функция и абстрактный класс

Часто в базовом классе виртуальная функция не определяется, а только объявляется. Чтобы подчеркнуть, что это делается намеренно, задается объявление

virtual void FF(...) = 0;

Такая функция называется чистой виртуальной функцией. Если в классе задана чистая виртуальная функция, то определить объект этого класса нельзя!

Например, задан класс:

class A

{ public:

virtual Print( ) = 0; // чистая виртуальная функция

...

};

Тогда следующий фрагмент – ошибочен:

{ A x; // ошибка!

...

}

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

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

A Func(B x, A y){..}

Ошибка! Ошибка!

5.4. Правила определения виртуальных функций

  1. Виртуальная функция в базовом классе либо определяется, либо объявляется как чистая виртуальная;

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

а) либо определяется,

б) либо заново объявляется как чистая виртуальная,

в) либо остается таковой по умолчанию.

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

virtual void FF(..) { }

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

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

Рассмотрим это свойство на следующем примере. Например, заданы классы

class A

{ …

public:

virtual void Print( ) = 0; // чистая виртуальная

};

class B: public A

{ ...

public:

virtual void Print( ) = 0; // осталась и в B чистой виртуальной

virtual int F( ) = 0; // новая чистая виртуальная функция

...

};

class C: public B

{ ...

public:

virtual int F( ) {return 3;}

virtual void Print( ) {cout << “\nКласс C:”;}

};

void main( ) // Что можно, что нельзя?

{A *pa; C z; C *pc = &z; B *pb;

pa = pc;

pa –> Print(); // можно: работает C::Print( ), объявленная в A, как чистая виртуальная

int k = pa –> F( ); // так нельзя: F() не объявлялась в базовом классе A никак!

pb = pc;

k = pb –> F(); // верно: именно в классе B, как базовом для C, F( ) была объявлена как виртуальная (чистая)

}

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

а) виртуальная функция вызывается не через указатель или ссылку, а через объект.

Например,

B y;

y.Print( ); // Ясно: B::Print( )

б) Виртуальная функция вызывается через указатель или ссылку на базовый класс, но уточняется именем класса. Например,

A x; B y; A *pa = &y;

pa –> B:: Print( ); // Невиртуальный вызов виртуальной функции

pa –> Print( ); // Виртуальный вызов виртуальной функции

в) Если виртуальная функция вызывается в конструкторе или деструкторе порожденного класса, то всегда вызывается функция базового класса, т.к. объект порожденного класса еще не создан или уже разрушен.

  1. Вызов виртуальной функции через ссылку на базовый класс также является виртуальным. Чаще всего такая ситуация возникает, когда типом аргумента некоторой внешней функции является ссылка на базовый класс, а в этой функции выполняется обращение к виртуальной функции. Фактическим аргументом в этом случае может быть объект любого порожденнного класса, где определена виртуальная функция, так как при вызове выполняется стандартное преобразование порожденного объекта к базовому.

Например, определена функция

void DoPrint(A &a)

{a.Print( ); // Виртуальный вызов функции Print

}

и в основной функции задан вызов ее с объектами разных классов:

void main( )

{A x(1, 1, 1); B y(2, 3, 1, 6, 4, 7);

DoPrint(x); // Выполнится A:: Print( );

DoPrint(y); // Выполнится B:: Print( );

}

  1. До сих пор речь шла о виртуальных функциях. Но виртуальными могут быть и операции, перегруженные в классе.

Например, в классе A определим виртуальную операцию ‘-‘ (изменить знак данных)

class A

{ ....

public:

virtual void operator -( )

{d1 = -d1; d2 = -d2; d3 = -d3;}

....

};

class B: public A

{ ...

public:

void operator-( )

{ // в том случае, если d1 будет в части protected можно и так :

// d1= -d1;d2= -d2;A::d3= -A::d3;d3= -d3;d4= -d4;

// то есть в общем случае нельзя.

// Но короче и грамотнее так:

A:: operator-( ); // работает базовая операция ‘-‘. Без A:: будет рекурсия

d3 = -d3; d4 = -d4;}

};

Примеры виртуальных вызовов операции ‘-‘:

A x(2, 2, 3), *pa;

B y(5, 4, 3, 2, 1);

pa = &y;

-(*pa); // виртуальный вызов операции ‘-’ через указатель на базовый класс для объекта y

pa –> Print(); // виртуальный вызов B::Print()

A &sa = y;

-sa; // виртуальный вызов операции ‘-‘ через ссылку sa

sa.Print( ); //виртуальный вызов функции Print( ) через ссылку

  1. Виртуальные функции могут быть и приватными.

Например, функция print( ) в классах A и B:

class A

{virtual void print( ) {cout << “\n********A*********\n”;}

protected: int a;

public:

A( ) {a = 1;}

virtual void Print( ) {print( ); cout << a;}

...

};

class B: public A

{ int b;

void print( ) {cout << “\n**********B********\n”;}

public;

B( ) {b = 2;}

void Print( ) {print( ); cout << a << ’ ‘ << b;}

};

При вызове виртуальной функции Print( ) будет вызываться и виртуальная приватная функция print( ). Например,

A x, *pa; B y;

pa = &x;

pa –> Print( ); // Работают функции Print( ) и print( ) класса A

pa = &y;

pa –> Print( ); // Работают функции Print( ) и print( ) класса B

Замечание. Удивительно другое. Если бы функция Print( ) класса B была задана так:

B:: Print( ) {print( );

A:: Print( );

cout << ’ ‘ << b;

}

то при виртуальном вызове

pa = &y;

pa –> Print( );

2 раза проработала бы виртуальная функция print( ) из класса B.

Почему? Понять можно, разобравшись с механизмом позднего связывания.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]