И.Г. Головин - Конспект лекций по курсу Языки программирования (1161120), страница 23
Текст из файла (страница 23)
это заместительВ Java невиртуальных функций просто не бывает. В С# и Delphi мы можем как заместить, таки не замещать.class X{public virtual void f(){…}}class Y:X { … public void f(){…}…}В С# нужно явно поставить ключевое слово override.Если его не написать, то компилятор будет выдавать предупреждения, которые можно«заткнуть», только поставив слово new.Довольно хорошая практика: все предупреждения должны трактоваться как ошибки.Если в определении языка описано, что какую-то инструкцию можно употреблять, незначит, что ее нужно употреблять.Мы можем поставить virtual после new, но это означает, что виртуальность начинаетсязаново с этого уровня.Собственно все про механизм виртуальности.
Вернемся к понятию абстрактный класс (АК).Вспомним пример – класс Figure.class Figure {protected:int x,ypublic:virtual void Draw();void Erase();void Move(int dx, int dy) {Erase(); x += dx; y += dy;Draw();143} //непонятно как будет работать универсальный метод Erase};class Point: public Figure {//переопределяем Draw()…}list <Figure*> objects; // статический список всех объектовforeach(objects.begin(),objects.end(),Draw);//метод reDrawМы не можем написать реализацию метода Draw для произвольной фигуры. Это хорошийкандидат на виртуальный метод.При таком описании произойдет ошибка.
Нет реализации метода Draw в классе Figure.Ошибку выдаст компоновщик.Для этого нужны абстрактные методы.virtual void Draw() = 0;Появилось понятие чисто виртуальной функции. У нее необязательно должно быть тело.Компилятор запретит создание объектов такого типа.Любой ОО язык поддерживает концепцию абстрактных классов.В языке C# и Java определение абстрактного класса:abstract classpublic abstract virtual void Draw();Абстрактный класс – это класс, перед описанием которого написано слово abstract.Объекты абстрактного класса заводить нельзя.В классе Figure лучше всего сделать все 3 функции виртуальными.Когда мы проектируем некоторую иерархию, то лучше всего на верхнем уровне сделатькласс, все члены которого – публичные чисто виртуальные (абстрактные) функции. Такиеклассы называют интерфейсами.Если в классе есть виртуальные методы, то деструктор должен быть виртуальным.
Если онбудет невиртуальным, то при работе через указатели:Figure* pfig;delete pfig;будет вызван не тот деструктор.virtual ~Figure();В 99,9% случаях, если в классе есть виртуальные функции, а деструктор являетсяневиртуальным, то это ошибка.144Рассмотрим теперь множество (set).class ISet {public:void Incl(T x) = 0;void Excl(T x) = 0;…Virtual ~ISet() {…};};Class Keys: public ISet,private set <string> {…};//это и есть в чистом виде инкапсуляцияИнтерфейс – чистый контракт.
Содержит только заглушки для методов.В языках C# и Java есть понятие interface.interface имя {объявление метода; (или константы)}class X extends:Yimplements список интерфейсовclass X: Y, I1, I2,…,IN {…}iostream – одна из первых иерархий, в которой нет ни одного виртуального метода.ios, istream, ostream, iostream, fstream, strstreamНо большинство других иерархий требует наличия виртуальных, абстрактных методов,классов, функций.Фабричный класс – методы: создать меню, создать окно, …Программирование с помощью интерфейсов – легкий способ создания абстрактных классов.Эту реализацию поддерживают почти все языки (может быть, кроме Delphi).Что плохого в множественном наследовании? С конфликтом имен как-то можноразобраться. В языке Objective C реализовать множественное наследование не удастся:145AAAXYAXAYXfWYWWW * pw = pw -> f(); //Какой адрес должен быть передан?X* px; Y* py;px = &w; py = &w;px->f();//будет работатьpy->f();В таблицу виртуальных функций добавляется еще одно поле – дельта (насколько смещатьуказатель this).
Появляются дополнительные накладные расходы.В ромбовидном случае:class w: public X, public Y {…}class X: virtual public A,Y: virtual public AВот поэтому современные языки множественное наследование не поддерживают, ониподдерживают интерфейсы.Draw – нарисоватьDraw – раздать картыJava: выбрать тот метод, что нравится, другой переименует.В C# делается так:I1:Process();I2:Process();class X: I1,I2 {Public void Process(){…}// неявная реализация интерфейсов}146class X: I1,I2 {void I1.Process(){…}// явная реализацияvoid I2.Process(){…}}X x = new X();I1 i1 = x;i1.Process();((I1)x)Process();class X:IEnumerable<T> {public T GetEnumerator<T>(){…}Object IEnumerable.GetEnumerator(){…}}147СПИСОК ИСПРАВЛЕНИЙ (ВЕРСИЯ 12 ЯНВАРЯ)с.
5 – исправлена схема архитектуры ЭВМ.с. 6 – исправлен пример на C (название константы, переменная i определяется вне цикла) иописание этого примера.с. 7 – исправлен абзац про прототипные языкис. 7 – исправлен абзац про RADс. 8 –поправлен заголовокс. 9 – убран непонятный абзац про C++с.9-11 – исправлен синтаксис в примерах Lispc. 41 – исправлено предложение «Область действия в классических языкахпрограммирования совпадает с областью действия».Спасибо за исправления Владу Шахуро, Михаилу Старцеву и Александру Фомину.148.