Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП Лекции PDF / ООП 09 Лек Виртуальные функции.pdf
Скачиваний:
64
Добавлен:
15.02.2015
Размер:
275.35 Кб
Скачать

Лекция 9

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

1

 

Л Е К Ц И Я 9

 

ВИРТУАЛЬНЫЕ ФУНКЦИИ______________________________________________________________ 1

Раннее и позднее связывание. Динамический полиморфизм ___________________________________ 1

Виртуальные функции___________________________________________________________________ 1 Виртуальные деструкторы _______________________________________________________________ 4 Абстрактные классы и чисто виртуальные функции___________________________________________ 5

ВИРТУАЛЬНЫЕ ФУНКЦИИ

Раннее и позднее связывание. Динамический полиморфизм

В C++ полиморфизм поддерживается двумя способами.

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

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

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

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

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

Для каждого полиморфного типа данных компилятор создает таблицу виртуальных функций и встраивает в каждый объект такого класса скрытый указатель на эту таблицу. Она содержит адреса виртуальных функций соответствующего объекта. Имя указателя на таблицу виртуальных функций и название таблицы зависят от реализации в конкретном компиляторе. Например, в Visual C++ 6.0 этот указатель имеет имя vfptr, а таблица называется vftable (от английского Virtual Function Table). Компилятор автоматически встраивает в начало конструктора полиморфного класса фрагмент кода, который инициализирует указатель на таблицу виртуальных функций. Если вызывается виртуальная функция, код, сгенерированный компилятором, находит указатель на таблицу виртуальных функций, затем просматривает эту таблицу и извлекает из нее адрес соответствующей функции. После этого производится переход на указанный адрес и вызов функции.

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

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

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

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

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

Выжол Ю.А.

Объектно-ориентированное программирование

Лекция 9

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

2

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

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

class Coord

 

Базовый класс координат

 

// базовый класс координат

{

 

 

protected:

 

// защищённые члены класса

double x , y ;

 

// координаты

public:

 

// открытые члены класса

Coord ( ) { x = 0 ; y = 0 ; }

// конструктор базового класса

void Input ( ) ;

 

// объявляет невиртуальную функцию

virtual void Print ( ) ;

// объявляет виртуальную функцию

} ;

 

 

void Coord :: Input ( )

// позволяет вводить координаты с клавиатуры

{

 

 

cout<<"\tx=";

cin>>x ;

// вводит значение x с клавиатуры

cout<<"\ty=";

cin>>y ;

// вводит значение y с клавиатуры

}

 

 

void Coord :: Print ( )

// выводит значения координат на экран

{

 

 

cout<<"\tx="<<x<<"\ty="<<y<<'\n' ;

}

Производный класс точки

class Dot : public Coord

// наследник класса координат

{

 

char name ;

// имя точки

public:

// открытые члены класса

Dot (char N ) : Coord ( ) { name = N ; }

// вызывает конструктор базового класса

void Input ( ) ;

// переопределяет невиртуальную функцию

void Print ( ) ;

// переопределяет виртуальную функцию

} ;

 

void Dot :: Input ( )

// позволяет вводить координаты точки с клавиатуры

{

 

char S [ ] ="Введите координаты точки "; // объявляет и инициализирует строку приглашения

CharToOem ( S , S ) ;

// преобразует символы строки в кириллицу

cout<<S<<name<<'\n';

// выводит на экран заголовок и имя точки

Coord :: Input ( ) ;

// вызывает функцию базового класса

}

 

void Dot :: Print()

// выводит значения координат точки на экран

{

 

char S [ ] ="Координаты точки ";

// объявляет и инициализирует строку заголовка

CharToOem ( S , S ) ;

// преобразует символы строки в кириллицу

cout<<S<<name<<" :";

// выводит на экран заголовок и имя точки

Coord :: Print ( ) ;

// вызывает функцию базового класса

}

 

class Vec : public Coord

Производный класс вектора

// наследник класса координат

{

 

char name [ 3 ] ;

// имя вектора

public:

// открытые члены класса

Vec ( char* pName ) : Coord ( ) { strncpy ( name , pName , 3 ) ; name [ 2 ] = '\0' ; }

void Input ( ) ;

// переопределяет невиртуальную функцию

void Print ( ) ;

// переопределяет виртуальную функцию

} ;

 

void Vec :: Input()

// позволяет вводить проекции вектора с клавиатуры

Выжол Ю.А.

Объектно-ориентированное программирование

Лекция 9 Виртуальные функции 3

{

char S [ ] ="Введите проекции вектора "; // объявляет и инициализирует строку приглашения

CharToOem ( S , S ) ;

// преобразует символы строки в кириллицу

cout<<S<<name<<'\n';

// выводит на экран приглашение и имя вектора

Coord :: Input ( ) ;

// вызывает функцию базового класса

}

 

void Vec :: Print ( )

// выводит значения проекций вектора на экран

{

 

char S [ ] = "Проекции вектора ";

// объявляет и инициализирует строку заголовка

CharToOem ( S , S ) ;

// преобразует символы строки в кириллицу

cout<<S<<name<<" :";

// выводит на экран заголовок и имя вектора

Coord :: Print ( ) ;

// вызывает функцию базового класса

}

 

В приведённом примере объявлен базовый класс Coord и два производных класса Dot и Vec. Функция Print ( ) в производных классах является виртуальной, так как она объявлена виртуальной в базовом классе Coord. Функция Print ( ) в производных классах Dot и Vec переопределяет функцию базового класса. Если производный класс не предоставляет переопределенной реализации функции Print ( ), используется реализация по умолчанию из базового класса.

Функция Input ( ) объявлена невиртуальной в базовом классе Coord и переопределена в производных классах Dot и Vec.

void main ( )

{

Coord* pC = new Coord ( ) ;

// объявляет указатель на координаты и выделяет память

Dot* pD = new Dot ( 'D' ) ;

// объявляет указатель на точку и выделяет память

Vec* pV = new Vec ("V" ) ;

// объявляет указатель на вектор и выделяет память

pC->Input ( ) ;

// вызывает невиртуальную функцию Coord :: Input ( )

pC->Print ( ) ;

// вызывает виртуальную функцию Coord :: Print ( )

pC = pD ;

// указатель на координаты получает адрес объекта типа точки

pC->Input ( ) ;

// вызывает невиртуальную функцию Coord :: Input ( )

pC->Print ( ) ;

// вызывает виртуальную функцию Dot :: Print ( )

pC = pV ;

// указатель на координаты получает адрес объекта типа вектора

pC->Input ( ) ;

// вызывает невиртуальную функцию Coord :: Input ( )

pC->Print ( ) ;

// вызывает виртуальную функцию Vec :: Print ( )

}

 

В приведённом примере указатель на координаты pC поочерёдно принимает значения адреса объектов координат, точки и вектора. Несмотря на то, что тип указателя pC не изменяется, он вызывает различные виртуальные функции в зависимости от своего значения.

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

Необходимо отметить, что операция присвоения pC = pD, в которая использует операнды различных типов (Coord* и Dot*) без преобразования, возможна только для указателя на базовый класс в левой части. Обратная операция присвоения pD = pC недопустима и вызывает ошибку синтаксиса.

При выполнении программа выводит на экран:

x = 1

 

 

 

y = 1

 

 

 

x = 1

y = 1

 

 

x = 2

 

 

 

y = 2

 

 

 

Координаты точки D :

x = 2

y = 2

x = 3

 

 

 

y = 3

 

 

 

Проекции вектора V :

x = 3

y = 3

При вызове функции с помощью указателей и ссылок, применяются следующее правила:

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

вызов невиртуальной функции разрешается в соответствии с типом указателя или ссылки.

Виртуальные функции вызываются только для объектов, принадлежащих некоторому классу. Поэтому

нельзя объявить глобальную или статическую функцию виртуальной. Ключевое слово virtual может

Выжол Ю.А.

Объектно-ориентированное программирование