class x{ protected: int a; public: x(int a1){a=a1;}; ............... virtual void show(){ cout<<"\nфункция класса x"; } .............. }; class y: public x{ protected: int b; public: y(int a1,int b1):x(a1){b=b1;}; void show(){ cout<<"\nфункция класса y"; } ......................... }; class z: public y{ protected: int c; public: z(int a1,int b1, int c1):y(a1,b1){c=c1;}; void show(){ cout<<"\n функция класса z"; } ........................ }; void show(x&x1){ x1.show(); } void main(){ x x1(1); y y1(2,3); z z1(4,5,6); x1.show();//.. класс x y1.show();//.. класс y z1.show();//.. класс z x *px; px=&x1; px->show();//..класс x px=&y1; px->show();//..класс y px=&z1; px->show();//..класс z y *py; py=&x1;//ошибка py=&y1; py->show();//..класс y py=&z1; py->show();//..класс z show2(x1);//..класс x show2(y1);//..класс y show2(z1);//..класс z }; Позднее связывание Во время компиляции с функцией show2 неизвестно, какой метод будет вызван, определение адреса метода происходит во время исполнения программы. Такой механизм называется поздним связыванием. Позднее связывание замедляет выполнение программы. Реализация позднего связывания: для каждого класса (не объекта), содержащего хотя бы один виртуальный метод, компилятор создаёт таблицу виртуальных методов (vtbl), в которой для каждого виртуального метода записан его адрес в памяти. Адреса методов располагаются в таблице в порядке их описания в классах, поэтому адрес каждого конкретного виртуального метода имеет в таблице виртуальных методов одно и то же смещение для каждого класса в цепочке иерархии. Каждый объект содержит скрытое дополнительное поле ссылки на таблицу виртуальных методов, называемое обычно vptr. Оно заполняется конструктором при создании объекта. Для этого компилятор добавляет в начало тела конструктора соответствующие конструкции. На этапе выполнения в момент обращения к методу его адрес выбирается из таблицы. class1 <----------------- class2 vtbl vtbl +---+ +---+ |Vm1+---------+ |Vm1+-------+ +---+ | +---+ | |Vm2+-------+ | |Vm2+-----+ | +---+ | | +---+ | | |Vm3+----+ | | |Vm3+---+ | | +---+ | | | +---+ | | | +------+ | | | |Vm4+-+ | | | |кодVm1|<+ | | +---+ | | | | +------+ | | +------+ | | | | | | |кодVm1|<+ | | | +------+ | | +------+ | | | |кодVm2|<---+ | | | | +------+ | +------+ | | | | |КодVm2|<--+ | | +------+ | +------+ | | |кодVm3|<-----+ | | +------+ +------+ | | |КодVm3|<----+ | +------+ | | +------+ | |КодVm4|<------+ +------+ class *O1; O1=new Class1(); +------------- | | ---------------+ Схема работы виртуальных методов в приложениях Родительский класс обозначает интерфейс при помощи виртуальных функций, а порождённые классы обеспечивают набор реализаций для этого интерфейса. Пример: создать класс "товар" с данными "название", "год выпуска", "количество", "стоимость одной единицы товара". Нужно определить функцию "остаток", которая может выполнять различные действия в зависимости от вида товара. class Goods{ protected: char *name; int year; int quant; double price; public: Goods(char*,int,int,double); void setQ(int); void setP(double); char* getN(); ................ virtual void showRest(){ cout<<"\nОстаток товара"; } .................... }; class GSug: public Goods{ public: GSUg(char *n,int g,int q, double p):Goods(n,y,q,p){} void showRest(){ cout<<"\nостаток товара \"сахар\" в количестве"<<50*quant<<" кг"; } }; Нужно определить функцию-остаток для сахара, вывести название и общий вес. Нужно определить функцию-остаток для телевизоров, при этом должно быть сформулировано название и общая стоимость. class GTV: public Goods{ public: GTV (char *n, int y, int q, double p):Goods(n,y,q,p){} void showRest(){ cout<<"\n осталось"<showRest(); pg->&t1; pg->showRest(); ................. }; Пустые виртуальные функции (NULL-виртуальные функции) Пустые виртуальные функции используются в цепочке наследования в том случае, когда функция в некотором промежуточном классе иерархии не нужна, но должен сохраниться механизм виртуальных функций для следующих потомков. class A{ ............. public: ................... virtual void f1(..){..} ................... }; class B: public A{ ................. public: void f1(...){} ............. }; class C:public B{ .................. public: void f1(...){......} .......... }; void main(){ A *pA=new A(...); B *pB=new B(...); C *pC=new C(...); pa->f1(); pB->f1();//нет действия pC->f1(); }; Абстрактные классы и чистые виртуальные функции Класс, который может использоваться только в качестве базового для других классов, называется абстрактным классом. Абстрактный класс содержит одну либо несколько чистых виртуальных функций. Чистая виртуальная функция - это функция-член класса, тело которой определено следующим образом: =0 (чистый спецификатор или чистый инициализатор). Для чистой виртуальной функции не приводят действительное определение. Предполагается, что она будет переопределена в порождённых классах. К абстрактным классам применимы следующие правила: абстрактный класс не может использоваться в качестве фактического параметра функции или типа возвращаемого значения. Формальный класс нельзя использовать в явном преобразовании; нельзя определить представителя абстрактного класса, то есть локальную или глобальную переменную, элемент данных. Можно определить ссылку или указатель или ссылку на абстрактный класс. Если класс, производный от абстрактного, не определяет все чистые виртуальные функции абстрактного класса, то он также является абстрактным. Если у ряда классов имеется ряд общих аттрибутов, то для упрощения их описания можно ввести общий для них родительский класс, сосредоточив в нём общие данные и поведение. Создание объекта такого родительского класса лишено смысла. Жилой дом, спортивный зал, стадион, теннисный корт. +---------------+ +---------------+ +---------------------+ +-------+ |Жилой дом |---------->|Сооружение |<------------|Спортивное сооружение|<-------------|Стадион| +---------------+ +---------------+ +---------------------+ +-------+ ^ ^ +--------------+ | +----------------+Спортивный зал| | +--------------+ | +--------------+ +---------------------+Теннисный корт| +--------------+ class building{ protected: char *addr; double area; char* owner; public: building(char*,double,char*); char* getAddr(); double getArea(); char* getOwner(); void setOwner(char*); virtual void getInfo()=0; virtual double getPr()=0; }; class house:public building{ protected: int app;//квартира int lodger;//жильцы int appf;//своб. квар. double price; public: house(char*,double,char*,int,double); void setLod(int l, int a=0){ lodger+=l; appf-=a; } void setFree(int l,int a=0{ lodger-=l; appf+=a; void getInfo(){ cout<<"\nжилой дом по адресу "<