Бьерн Страуструп. Язык программирования С++



бет49/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   45   46   47   48   49   50   51   52   ...   124

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


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

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

которые могут переопределяться в любом производном классе. Транслятор

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

применяемыми к ним функциями:


class employee {

char* name;

short department;

// ...


employee* next;

static employee* list;

public:

employee(char* n, int d);



// ...

static void print_list();

virtual void print() const;
};
Служебное слово virtual (виртуальная) показывает, что функция print()

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

версии при вызове print() - это задача транслятора.

Тип функции указывается в базовом классе и не может быть

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

должно даваться для того класса, в котором она была впервые

описана (если только она не является чисто виртуальной функцией,

см. $$6.3). Например:


void employee::print() const

{

cout << name << '\t' << department << '\n';



// ...

}
Мы видим, что виртуальную функцию можно использовать, даже если

нет производных классов от ее класса. В производном же классе

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

не нужна. При построении производного класса надо определять

только те функции, которые в нем действительно нужны:


class manager : public employee {

employee* group;

short level;

// ...


public:

manager(char* n, int d);

// ...

void print() const;



};
Место функции print_employee() заняли функции-члены print(), и она

стала не нужна. Список служащих строит конструктор employee ($$6.2.2).

Напечатать его можно так:
void employee::print_list()

{

for ( employee* p = list; p; p=p->next) p->print();



}
Данные о каждом служащем будут печататься в соответствии с типом

записи о нем. Поэтому программа


int main()

{

employee e("J.Brown",1234);



manager m("J.Smith",2,1234);

employee::print_list();

}
напечатает
J.Smith 1234

level 2


J.Brown 1234
Обратите внимание, что функция печати будет работать даже в том случае,

если функция employee_list() была написана и оттранслирована еще до того,

как был задуман конкретный производный класс manager! Очевидно, что для

правильной работы виртуальной функции нужно в каждом объекте класса

employee хранить некоторую служебную информацию о типе. Как правило,

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

Этот указатель хранится только для объектов класса с виртуальными

функциями, но не для объектов всех классов, и даже для не для всех

объектов производных классов. Дополнительная память отводится только

для классов, в которых описаны виртуальные функции. Заметим, что

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

память.


Если в вызове функции явно указана операция разрешения

области видимости ::, например, в вызове manager::print(),

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

вызов привел бы к бесконечной рекурсии. Уточнение имени функции

дает еще один положительный эффект: если виртуальная функция

является подстановкой (в этом нет ничего необычного), то в вызове

с операцией :: происходит подстановка тела функции. Это

эффективный способ вызова, который можно применять

в важных случаях, когда одна виртуальная функция

обращается к другой с одним и тем же объектом. Пример такого

случая - вызов функции manager::print(). Поскольку тип объекта

явно задается в самом вызове manager::print(), нет нужды определять

его в динамике для функции employee::print(), которая и будет

вызываться.



6.3 Абстрактные классы


Многие классы сходны с классом employee тем, что в них можно дать

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

классы. Некоторые, например, класс shape, представляют

абстрактное понятие (фигура), для которого нельзя создать объекты.

Класс shape приобретает смысл только как базовый класс в некотором

производном классе. Причиной является то, что невозможно дать

осмысленное определение виртуальных функций класса shape:


class shape {

// ...


public:

virtual void rotate(int) { error("shape::rotate"); }

virtual void draw() { error("shape::draw"): }

// нельзя ни вращать, ни рисовать абстрактную фигуру

// ...

};
Создание объекта типа shape (абстрактной фигуры) законная, хотя



совершенно бессмысленная операция:
shape s; // бессмыслица: ``фигура вообще''
Она бессмысленна потому, что любая операция с объектом s приведет

к ошибке.

Лучше виртуальные функции класса shape описать как чисто

виртуальные. Сделать виртуальную функцию чисто виртуальной можно,

добавив инициализатор = 0:
class shape {

// ...


public:

virtual void rotate(int) = 0; // чисто виртуальная функция

virtual void draw() = 0; // чисто виртуальная функция

};
Класс, в котором есть виртуальные функции, называется абстрактным.

Объекты такого класса создать нельзя:
shape s; // ошибка: переменная абстрактного класса shape
Абстрактный класс можно использовать только в качестве базового

для другого класса:


class circle : public shape {

int radius;

public:

void rotate(int) { } // нормально:



// переопределение shape::rotate

void draw(); // нормально:

// переопределение shape::draw
circle(point p, int r);

};
Если чисто виртуальная функция не определяется в производном

классе, то она и остается таковой, а значит производный класс тоже

является абстрактным. При таком подходе можно реализовывать

классы поэтапно:
class X {

public:


virtual void f() = 0;

virtual void g() = 0;

};
X b; // ошибка: описание объекта абстрактного класса X
class Y : public X {

void f(); // переопределение X::f

};
Y b; // ошибка: описание объекта абстрактного класса Y
class Z : public Y {

void g(); // переопределение X::g

};
Z c; // нормально
Абстрактные классы нужны для задания интерфейса без уточнения

каких-либо конкретных деталей реализации. Например, в операционной

системе детали реализации драйвера устройства можно скрыть

таким абстрактным классом:


class character_device {

public:


virtual int open() = 0;

virtual int close(const char*) = 0;

virtual int read(const char*, int) =0;

virtual int write(const char*, int) = 0;

virtual int ioctl(int ...) = 0;

// ...


};
Настоящие драйверы будут определяться как производные от класса

character_device.

После введения абстрактного класса у нас есть все основные

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





Достарыңызбен бөлісу:
1   ...   45   46   47   48   49   50   51   52   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет