Лекция 19 (лекции (2002))

2019-09-19СтудИзба

Описание файла

Файл "Лекция 19" внутри архива находится в папке "лекции (2002)". Документ из архива "лекции (2002)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .

Онлайн просмотр документа "Лекция 19"

Текст из документа "Лекция 19"

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

Пример: отрисовка фигур.

Figure

Point Circle Rect

Пусть у нас есть некоторые объекты, общее название для которых, а стало быть и название базового класса - Figure. Известно, что каждая фигура умеет сама себя отрисовывать на экране, т.е. обладает методом

void Draw( );

Каждая фигура умеет себя двигать, т.е. обладает методом

void Move(int dx, int dy);

Каким образом фигура отрисовывается на экране по абстрактной фигуре ничего сказать нельзя. У каждой фигуры есть координаты точки привязки

int X, Y;

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

Figure * a[];

для того, чтобы отрисовать весь массив

for(int i=0; i<N; i++)

a[i]->Draw();

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

Гибкость с точки зрения развития заключается в следующем. Этот код написан в предположении, что у нас есть классы Point, Circle, Rect. Если мы выведем еще какой-то тип данных (например, FillRect – заполненный прямоугольник).

Figure

Point Circle Rect

FillRect

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

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

case …

when Circle => Draw

when … => …

when … => …

Для каждого нового типа мы должны будем добавлять новый when. Это чревато тем, что при появлении нового типа данных (например, новой фигуры) мы должны перелопатить весь проект, отловить все подобного рода места, и сделать в них соответствующую модификацию. В случае объектно-ориентированного программирования с динамическим связыванием этого делать не нужно. Поэтому как программисты, так и менеджеры, начиная с конца 80-ых г.г. с удовольствием ухватились за возможности, которые предоставляют соответствующие языки.

В языке С++ методы делятся на виртуальные и не виртуальные. Если функция описана в каком-то базовом классе как виртуальная

virtual void f( ){…}

то любое ее переопределение (замещение – override) в базовом классе с тем же прототипом обязательно является виртуальной функцией. Т.е. в С++ сделать ее не виртуальной нельзя.

В языке Java все методы по определению являются виртуальными, поэтому ключевого слова virtual там нет. Все методы в языке Java имеют только динамическое связывание.

Языки Delphi и C# пошли примерно по пути языка С++, но с некоторыми модификациями. И в Delphi, и в C# методы делятся на виртуальные и не виртуальные. Поэтому в этих языках есть ключевое слово virtual. Но и в Delphi, и в C# функция с тем же самым прототипом, не обязательно будет замещать виртуальную функцию в производном классе. В случае, если мы хотим, чтобы было динамическое замещение, то в языках C# и Delphi надо писать ключевое слово override. В языке С# если даже мы напишем

virtual void f( ){…};

в производном классе, то компилятор выдаст предупреждение потому, что, не смотря на то, что f объявлена виртуальной, мы явно не сказали, что она замещает соответствующую функцию в базовом классе. Предупреждение не будет выдано, если мы напишем override (тогда ключевое слово virtual писать не обязательно), либо если мы напишем new. new говорит о том, что мы не хотим замещать эту функцию. Т.е. начиная с этого нового класса в иерархии f приобретает совершенно другой смысл. Это немного более гибкий механизм. Подход языка С++ к реализации иерархии классов более строг. И в Delphi, и в C# от этого более строгого подхода отказались. Если нас не устраивает иерархия базовых классов в языке С++, единственная возможность – переделать реализацию этих классов. В языке C# можно отказаться от наследия базовых классов и внести свою собственную семантику в соответствующую операцию. Прежде всего это связано с тем, что если нам поставляется какая-то библиотека, даже если она поставляется в исходных текстах, теоретически мы не имеем права менять соответствующие функции в библиотеке. Точно также мы не имеем права ничего менять в NET.Framework. Но, если нас не устраивает функциональность NET.Framework, мы можем переопределить соответствующие функции.

Когда мы говорим о динамическом связывание, речь идет исключительно о способе вызова. Сами по себе реализации функций (тела функций) и в виртуальном, и в не виртуальном случае представляют собой одно и то же. Т.е. речь идет исключительно о механизме виртуального вызова. В языке С++, если у нас есть класс Х и в нем виртуальная функция f

Х х;

то

x:f( );

- это не виртуальный вызов. А вызов через указатель, или, соответственно, через ссылку

X * px; X & xx;

px->f( ); xx.f( );

виртуальный. Т.е. динамический тип в языке С++ имеется только у указателей и ссылок. В Java, C# и Delphi объявление

Х х;

вводит не сам объект, а ссылку, и инициализируется она следующим образом

X x = new X( );

Естественно, что все вызовы вида

x:f( );

по определению будут динамическими. Иногда динамический вызов можно снимать. В С++ виртуальность вызова можно снимать в функциях-членах того же самого класса. Это совершенно обычная ситуация потому, что довольно часто механизм реализации виртуальных функций следующий. Есть класс Х и в нем виртуальная функция f. Из него выведен класс Y и в нем есть виртуальная функция f. Мы можем либо полностью переписать функцию f, либо, в некоторых случаях, мы можем использовать и функциональность базовых функций, при этом добавляя в них что-нибудь еще. Например, тот же самый метод Draw может реализовываться различным образом. Если у нас есть базовый тип Rect и в нем есть метод Draw. Мы из этого типа выводим FillRect, и одна из возможных реализаций метода Draw для FillRect следующая: вызвать метод Draw для Rect, который просто обрисует рамку вокруг прямоугольника, а затем вызвать какой-то метод, который заполняет каким-то шаблоном внутренность этого прямоугольника. Нам необходимо добавить некоторую функциональность в метод Draw, но при этом мы используем и старый метод. Для этого и служит механизм снятия виртуального вызова. Он в языке С++ применяется только к функциям–членам самого этого класса. Т.е. если мы хотим вызвать в методе Draw для FillRect метод Draw для типа данных Rect просто пишем

Rect::Draw( );

Такая конструкция снимает виртуальность. Ее нельзя применять извне функций–членов.

В C# и Java в принципе тоже можно снимать виртуальный вызов, но в некоторых частных случаях. В языке Java при описании метода можно задать ключевое слово final:

final void f( ){…}

Поскольку в языке Java все методы связаны только динамически, при любом переопределении метода речь идет именно о его замещении. Ключевое слово final говорит о том, что этот метод в производных классах нельзя замещать. В языке С# есть аналогичное по смыслу ключевое слово sealed (запечатанный). Это означает, что любая попытка переопределить в производном классе соответствующий метод приводит к выдаче ошибки компиляции. Т.е. компилятор не позволит переопределить соответствующий метод. final и sealed нужны, во-первых, из соображений безопасности. Например, если у нас есть какой-то механизм проверки, то мы можем в произвольном классе его заместить и ввести свой, и таким образом обойти тот алгоритм, который нам задан с самого начала. Если создателю класса это не по нутру, он может запечатать или закрыть соответствующий метод для расширения, и тогда мы можем использовать только его реализацию. Во-вторых, как мы увидим чуть позже, динамическое связывание сводится к косвенной ссылке. Страуструп приводит для реализации языка С++ такую цифру: порядка 8 "лишних" ассемблерных операций с точки зрения машинного кода добавляется при виртуальном вызове (при динамическом связывании). Т.е. в общем случае динамическое связывание влечет за собой определенного рода расходы. В принципе возможны случаи, если компилятор видит, что в классе Х метод f является финальным или запечатанным, и если есть определение

X x = …;

при вызове

x.f( );

компилятор как в С#, так и в Java имеет право вставлять здесь не виртуальный вызов. Это так потому, что, т.к. f является финальным или соответственно запечатанным методом, и речь идет об объекте класса Х, ясно, что вызывается именно эта функция. Даже если динамический тип х больше Х, все равно f будет браться из класса Х. Поэтому компилятор вполне может в данном случае выполнить вызов

X.f( );

Это некоторый вариант оптимизации. Все доступные реализации Java и C# используют эту оптимизацию. Т.е. механизм виртуальности иногда можно снимать.

Еще есть некоторые тонкости, связанные с замещением. Например, в языке Java, или в языке C++ рассмотрим такую ситуацию:

class X{

virtual void f(int);

};

class Y: public X{

virtual void f(string &);

};

Класс Y не замещает функцию f. И в языке С++, и в языке Java такая ситуация является допустимой, и в данном случае произойдет перекрытие.

Y y; String s;

В данном случае, не смотря на то, что речь идет о двух вложенных областях действия, вполне можно вызывать и

y.f(1);

и

y.f(s);

И это не будет являться ошибкой.

В случае языка C# ситуация следующая: замещение в языке C# идет не по сигнатурам (сигнатура – это прототип), а по именам. Если функции являются виртуальными, здесь имеет место механизм замещения, и в языке C# в рассмотренном случае будет выдана ошибка. Т.е., если мы переопределяем функцию f даже с другой сигнатурой, то новая функция f скрывает функцию f из базового класса (механизм скрытия). И у той, и у другой точки зрения есть свои противники, и есть свои защитники. В любом случае (даже в случае языка C#) можно получить доступ к старой функции f за счет механизма преобразования типа. Т.е., если для объекта у нам хочется вызвать функцию y.f(1), нам нужно преобразовать ссылку на производный класс в ссылку на базовый класс

((X)y).f(1);

В C# взяли синтаксис приведения типов С.

Методы реализация динамического связывания в различных ЯП.

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

class X{

virtual void f( );

virtual void g( );

}

В этом классе 2 виртуальных функции.

Свежие статьи
Популярно сейчас
А знаете ли Вы, что из года в год задания практически не меняются? Математика, преподаваемая в учебных заведениях, никак не менялась минимум 30 лет. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5224
Авторов
на СтудИзбе
426
Средний доход
с одного платного файла
Обучение Подробнее