Правила вызова методов (функций)
Переопределение виртуальных функций
Виртуальные методы (функции)
Виртуальные функции объявляются с помощью ключевого слова virtual. Это слово при объявлении функции определяет для нее механизм позднего связывания. Виртуальными могут быть только не статические функции-члены, так как характеристика virtual наследуется.
Виртуальная функция является обычным выполняемым кодом. Вызывается так же, как и другие функции. В порожденном классе она может быть переопределена, то есть для нее может быть написано новое тело.
Функция, объявленная в производном классе, переопределяет виртуальную функцию в базовом классе только тогда, когда имеет то же имя и работает с тем же количеством и типом аргументов, что и виртуальные функции базового класса. Если они отличаются хоть одним аргументом, то функция в производном классе считается совершенно другой (новой) и переопределения не происходит.
Это трудно уловимая ошибка, когда программист, считая, что он переопределяет виртуальную функцию базового класса, на самом деле, ошибившись в одном каком либо знаке заголовка функции – определяет новую, в C++ никак не “отлавливается” транслятором. В C#, для предотвращения подобной ошибки, в отличие от C++, необходимо, явно указывать с помощью ключевого слова override, что переопределяется функция базового класса. Встретив ключевое слово override, транслятор проверяет, имеется ли функция с таким заголовком среди виртуальных функций базового класса. В случае не совпадения заголовков выводится сообщение об ошибке компиляции.
Если функция объявлена virtual, то это свойство передается всем переопределениям в порожденных классах.
Переопределять виртуальные функции в порожденном классе необязательно. В этом случае функция базового класса становится функцией производного класса (по наследству).
Можно с помощью ключевого слова sealed запретить переопределение функции базового класса в производном классе. В этом случае попытка переопределения приведет к ошибке трансляции.
Если метод не виртуальный, то вызывается метод типа (класса), на который указывала ссылка в момент ее объявления.
Если метод является виртуальным, то при выполнении кода будет проверяться, куда на самом деле указывает ссылка (на какой именно объект). Затем проверяется экземпляром, какого класса является этот объект и вызывается соответствующий метод этого класса.
Пример:
using System;
namespace ConsoleApplication8
{
class CA
{
string nameClass;
public CA()
{
nameClass = "Class CA ";
}
public void Fnc()
{
Console.WriteLine(nameClass+"Fnc()");
}
public virtual void Fvrt()
{
Console.WriteLine(nameClass+"Fvrt()");
}
}
class CB : CA
{
string nameClass;
public CB()
{
nameClass = "Class CB ";
}
public new void Fnc()
{
Console.WriteLine(nameClass+"Fnc()");
}
public override void Fvrt()
{
Console.WriteLine(nameClass+"Fvrt()");
}
}
class Class1
{
static void Shw(CA m)
{
Console.WriteLine("****Shv******");
m.Fnc(); //Раннее связывание
m.Fvrt(); //Позднее связывание
}
[STAThread]
static void Main(string[] args)
{
CA a = new CA();
CB b = new CB();
a.Fnc(); // CA-Fnc()
a.Fvrt(); // CA-Fvrt()
b.Fnc(); // CB-Fnc()
b.Fvrt(); // CB-Fvrt()
Shw(a); // CA-Fnc()
// CA-Fvrt()
Shw(b); // CA-Fnc()
// CB-Fvrt()
}
}
}
Результат работы приложения:
Class CA Fnc()
Class CA Fvrt()
Class CB Fnc()
Class CB Fvrt()
****Shv******
Class CA Fnc()
Class CA Fvrt()
****Shv******
Class CA Fnc()
Class CB Fvrt()
В приведенном примере в классе родителе и производном классе содержатся функции с одинаковыми именами. Одна из этих функций Fvrt() является виртуальной и переопределяется в производном классе. Поэтому, в функции Shw , транслятор должен при вызове функции Fvrt() использовать механизм позднего связывания. Какая именно функция Fvrt() класса CA или CB будет вызвана на этапе выполнения приложения, зависит от объекта, который передается функции Shw() с помощью ссылки в качестве параметра. Вторую не виртуальную функцию Fnc() транслятор жестко свяжет еще на этапе компиляции с классом CA, так как функция Shw() принимает в качестве параметра ссылку на класс СА.
Абстрактные классы
Иногда, например, при разработке коллективного проекта, или создании библиотек классов, бывает на начальном этапе неясно, как реализовать методы. Поэтому создаются базовые классы, содержащие абстрактные методы, то есть методы, не имеющие тела (кода); При объявлении абстрактного метода используется модификатор abstract. Абстрактный метод автоматически становится виртуальным.
Например:
abstract void func();
Использование virtual совместно с abstract недопустимо – приведет к возникновению ошибки на этапе трансляции.
Модификатор abstract не применим к static.
Класс, содержащий один или более методов abstract, также должен быть объявлен как абстрактный:
abstract class abstr
{
int x;
public abstract void func();
:
}
Абстрактные классы создаются для нужд производных классов.
Поскольку абстрактный класс не определен полностью, объекты этого класса создать невозможно.
При объявлении класса, производного от абстрактного класса, все абстрактные методы должны быть переопределены.
Таким образом, абстрактный класс определяет интерфейсы некоторых методов, но не их реализацию.
Вопросы:
1. С помощью, каких функций, реализуется специальный полиморфизм?
2. Что означает раннее связывание вызовов функций?
3. Понятие позднего связывания функций?
4. С помощью какого ключевого слова необходимо объявить функцию, чтобы транслятор применил к ней механизм позднего связывания?
5. С помощью какого ключевого слова сообщается транслятору, что вы переопределяете функцию базового класса?
6. Как реализуется чистый полиморфизм?
7. Чем отличаются перегруженные функции от виртуальных функций?
8. Чем отличается сокрытие функции базового класса от переопределения функции базового класса?
9. Как можно запретить переопределение виртуальной функции в производных классах?
10. Чем отличается абстрактная функция от виртуальной функции?