Виртуальные функции. Абстрактные классы. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Виртуальные функции. Абстрактные классы.



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

#include <iostream>

using namespace std;

 

class rectangle {

protected:

float x,y,height,width;

public:

rectangle(float xbase, float ybase, float h, float w):

x(xbase), y(ybase), height(h), width(w) { }

float area() { return height*width; }

virtual void printarea() {

cout<<"rectangle area="<<area()<<endl;

}

};

class colorsquare: public rectangle {

private:

int color;

public:

colorsquare(float xbase, float ybase, float h, int c):

rectangle(xbase,ybase,h,h), color(c) { }

int getcolor() {return color;}

void printarea() {

cout<<"square area="<<rectangle::area()<<endl;

}

};

void main() {

rectangle a(0,0,10,20),*refa;

refa=&a;

refa->printarea();

colorsquare b(0,0,10,0xffffffff), *refb;

refb=&b;

refb->printarea();

refa=refb;

refa->printarea();

}

 

Единственное отличие – в объявлении функции printarea базового класса. Ключевое слово virtual служит спецификатором функции и как раз предоставляет механизм динамического выбора перегруженных функций. Результат работы данной программы следующий:

 

rectangle area=200

square area=100

square area=100

 

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

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

При отсутствии функции-члена производного типа по умолчанию используется виртуальная функция базового класса. В нашем примере при отсутствии определения функции printarea в классе colorsquare всегда будет вызываться эта же функция базового класса.

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

Деструкторы могут быть виртуальными. Если класс имеет хотя бы одну виртуальную функцию, рекомендуется деструкторы также объявлять виртуальными.

Дополним базовый класс rectangle деструктором:

virtual ~rectangle() {cout<<"in rectangle\n";}

Также добавим деструктор в производный класс (виртуальность в нем объявлять уже нет необходимости):

~colorsquare() {cout<<"in colorsquare\n";}

Определим главную функцию таким образом, чтобы продемонстрировать динамическое создание и уничтожение объектов с помощью операторов new и delete:

 

void main() {

rectangle *refa;

refa=new rectangle(0,0,10,20);

colorsquare *refb;

refb=new colorsquare(0,0,10,0xffffffff);

delete refa;

refa=refb;

delete refa;

}

 

Результат работы программы:

 

in rectangle

in colorsquare

in rectangle

 

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

 

in rectangle

in rectangle

 

Здесь при уничтожении объекта производного класса вызывается деструктор только базового класса.

 

Иерархия типов обычно имеет корневой класс, содержащий некоторое число виртуальных функций. При этом они в большинстве случаев являются фиктивными функциями. Они имеют пустое тело в корневом классе, но в производных классах этим функциям придают смысл (в терминологии ООП это называется отложенным методом). Для таких функций в С++ введено понятие «чисто виртуальные функции» – это виртуальные функции, тело которых не определено. Объявляются они следующим образом:

 

virtual прототип_функции=0;

 

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

Если абстрактная функция не определена в производном классе, он также является абстрактным. Приведем пример:

 

class shape {

protected:

float dim1,dim2;

public:

virtual float area()=0;

shape(float d1,float d2): dim1(d1), dim2(d2) { }

};

class rectangle: public shape{

protected:

float x,y;

public:

rectangle(float xbase, float ybase, float h, float w):

shape(w,h), x(xbase), y(ybase) { }

float area() { return dim1*dim2; }

virtual void printarea() {

cout<<"rectangle area="<<area()<<endl;

}

virtual ~rectangle() {cout<<"in rectangle\n";}

};

 

Мы использовали предыдущую программу со следующими изменениями:

- добавили абстрактный класс shape, имеющий виртуальную функцию area и конструктор

- изменили класс rectangle, сделав его производным от shape. При этом изменился конструктор класса и по-новому определена функция area.

Если бы мы не определили функцию area в производном классе, он так и остался бы быть абстрактным.

Пространства имен

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

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

 

 

В этих случаях язык С++ рекомендует ввести пространство имен (namespace). Все имена, которые надо включить в одно пространство, записываются внутри именованного блока

 

namespace MyNames{

// Классы, отдельные функции, глобальные переменные.

const int MAXLEN = 9999;

void func();

class A;

}

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

Блок namespace с одним и тем же именем можно записать несколько раз каждый блок добавляет в пространство имен новые имена. Можно написатьдалее:

 

namespace MyNames{

doublе f(double);

}

 

 

В этом случае в пространстве имен MyNames будет четыре имени.

 

Уточнение имени

Вне своего пространства имена уточняются с помощью операции разрешения видимости:

 

if (k < MyNames::MAXLEN) fl(a[k]);

MyNames::func();

MyNames::A a = new MyNames::A();

 

Пространство имен можно сравнить с городом. Все улицы города должны носить разные имена. Но в разных городах названия улиц могут совпадать. В каждом городе есть Садовая улица, Шоссейная улица, Центральная ули­ца. Поэтому, говоря о разных городах, мы уточняем название улицы, добав­ляя город, в котором она расположена. Если же разговор идет об одном го­роде, в таком уточнении нет нужды, ясно, о какой улице идет речь.

Директива using namespace

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

 

using namespace MyNames;

if (k < MAXLEN) fl(a[k]);

func();

A a = new AO;

 

Увидев директиву using namespace, компилятор будет во всех следующих строках отыскивать встреченные имена в текущем пространстве и в про­странстве имен MyNames. Разумеется, имена в этих пространствах должны быть различны. Совпадающие имена придется уточнять именем пространст­ва имен.

Вложение пространств имен

Директиву using namespace можно записать и внутри блока, определяюще­го пространство имен:

namespace MyNewNames{

using namespace MyNames; void f2();

}

 

Тем самым пространство имен MyNames вкладывается в пространство имен MyNewNames. Имена из пространства имен MyNames теперь лежат в пространстве имен MyNewNames и можно написать:

 

if (k < MyNewNames::MAXLEN) fl(a[k]);

MyNewNames::func();

MyNewNames::A a = new MyNewNames::A();

 

Такие имена будут сначала отыскиваться в пространстве имен MyNewNames, затем в пространствах имен, указанных в директивах using namespace.

 

Объявление using

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

 

using MyNames::MAXLEN;

using MyNames::A;

if (k < MAXLEN) fl(a[k]);

MyNames::func();

A a = new AO;

Для имен из стандартной библиотеки классов языка С++ выделено пространство имен, названное std. В этой книге мы часто будем использовать данные имена и применять для сокращения записи директиву using namespace std.

 

Неименованное пространство имен

Можно определить и неименованное пространство имен:

namespace{

double fmod(double, int); const double FM = 2.4523;

}

На самом деле компилятор даст какое-то уникальное имя этому простран­ству. Поскольку к именам из этого пространства как-то надо обращаться, компилятор тут же вставит директиву using namespace. В результате мы получим что-то вроде:

 

namespace XXX{

double fmod(double, int);

const double FM = 2.4523;

}

using namespace XXX;

 

Вследствие этого именами fmod и fm из неименованного пространства имен можно будет пользоваться в пределах файла, в котором все это напи­сано. Итак, неименованное пространство имен ограничивает видимость имени файлом.

Псевдонимы пространства имен

Для уже введенного имени пространства имен можно создать псевдоним:

namespace mnn = MyNewNames;

Псевдоним можно использовать для сокращения записи — вместо MyNewNames:: MAXLEN писать mnn:: MAXLEN. Кроме того, псевдоним можно использовать так же, как мы обычно используем константы — при необходимости сменить истинное имя пространства имен, например, при обновлении библиотеки классов, нам достаточно сменить это имя только в одном месте.



Поделиться:


Последнее изменение этой страницы: 2016-12-11; просмотров: 407; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.224.64.226 (0.027 с.)