Иня_объекта_производноро_класса.show()
В обоих случаях выбор нужной функции выполняется при написании исходного текста программы и не изменяется после компиляции. Такой режим называется ранним или статическим связыванием.
Большую гибкость (особенно при использовании уже готовых библиотек классов) обеспечивает позднее (отложенное), или динамическое связывание, которое предоставляется механизмом виртуальных функций. Любая нестатическая функция базового класса может быть сделана виртуальной, если в ее объявлении использовать спецификатор virtual. Прежде чем объяснить преимущества динамического связывания, приведем пример. Опишем в базовом классе виртуальную функцию и введем два производных класса, где определим функции с такими же прототипами, но без спецификатора virtual. В следующей программе в базовом классе base определена виртуальная функция void vfun(int). В производных классах dirl, dir2 эта функция подменяется (override), т.е. определена по-другому:
//- виртуальная функция в базовом классе
#include "stdafx.h"
#include <iostream>
using namespace std;
struct base {
virtual void vfun(int i) {cout <<"\nbase::i - "<<i;}
};
void print_(base& pb){pb.fun(1);}
struct dir1: public base {
void vfun (int i){cout << "\ndirl::i - " << i; }
};
struct dir2: public base {
void vfun (int i){ cout << "\ndir2::i = " << i; }
};
void main(void)
{
base B, *bp = &B;
dir1 D1, *dp1 = &D1;
dir2 D2, *dp2 = &D2;
bp->vfun(1); // Печатает: base::i = 1
dp1->vfun(2); // Печатает: dirl::i = 2
dp2->vfun(3); // Печатает: dir2::i = 3
bp = &D1;
bp->vfun(4) ; // Печатает:dir1::i = 4
bp = &D2;
bp->vfun(5); // Печатает: dir2::i = 5
print_(B); // Печатает : base::i = 1
print_(D1); // Печатает : dir1::i = 1
}
Результат выполнения программы:
В примере надо обратить внимание на доступ к функциям vfun() через указатель bр на базовый класс. Когда bр принимает значение адреса объекта класса base, то вызывается функция из базового класса. Затем bр последовательно присваиваются значения ссылок на объекты производных классов &Dl, &D2, и выбор соответствующего экземпляра функции vfun() каждый раз определяется именно объектом.
Таким образом, интерпретация каждого вызова виртуальной функции через указатель на базовый класс зависит от значения этого указателя, т.е. от типа объекта, для которого выполняется вызов. В противоположность этому интерпретация вызова через указатель невиртуальной функции зависит только от типа указателя (это было показано в предыдущем примере с функцией fun()).
Виртуальными могут быть не любые функции, а только нестатические компонентные функции какого-либо класса. После того как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.
В производном классе нельзя определять функцию с тем же именем и с той же сигнатурой параметров, но с другим типом возвращаемого значения, чем у виртуальной функции базового класса. Это приводит к ошибке на этапе компиляции.
Если в производном классе ввести функцию с тем же именем и типом возвращаемого значения, что и виртуальная функция базового класса, но с другой сигнатурой параметров, то эта функция производного класса не будет виртуальной. В этом случае с помощью указателя на базовый класс при любом значении этого указателя выполняется обращение к функции базового класса (несмотря на спецификатор virtual и присутствие в производном классе похожей функции).
Сказанное иллюстрирует следующая программа:
#include "stdafx.h"
#include <iostream>
using namespace std;
struct base {
virtual void f1(void) { cout << "\nbase::f1"; }
virtual void f2(void) { cout << "\nbase::f2"; }
virtual void f3(void) { cout <<"\nbase::f3"; }
};
struct dir: public base {
// Виртуальная функция:
void fl(void) { cout << "\ndir::fl"; }
// Ошибка в типе функции:
// int f2(void) { cout << "\ndir::f2"; }
// Невиртуальная функция:
void f3(int i) { cout << "\ndir::f3::i = "<< i; }
};
void main (void) { base B, *pb = &B;
dir D, *pd = &D;
pb->f1();
pb->f2();
pb->f3();
pd->fl();
pd->f2();
// Ошибка при попытке без параметра вызвать
dir::f3(); // pd->f3(); pd->f3(0); pb = &D; pb->fl(); pb->f2(); pb->f3();
// Ошибочное употребление или параметра, или указателя:
pb->f3(3);
}
Результат выполнения программы:
base::f1 base::f2 base::f3 dir::f1 base::f2 dir::f3::i = 0 dir::f1 base::f2 base::f3
Обратите внимание, что три виртуальные функции базового класса по-разному воспринимаются в производном классе. dir::fl() -виртуальная функция, подменяющая функцию base: :fl(). Функция base::f2() наследуется в классе dir так же, как и функция base::f3 (). Функция dir::f3(int) - совершенно новая компонентная функция производного класса, никак не связанная с базовым классом. Именно поэтому невозможен вызов f3 (int) через указатель на базовый класс. Виртуальные функции base::f2() и base::f3() оказались не переопределенными в производном классе dir. Поэтому при всех вызовах без параметров f3() используется только компонентная функция базового класса. Функция dir::f3(int) иллюстрирует соглашение языка о том, что если у функции производного класса набор параметров отличается от набора параметров соответствующей виртуальной функции базового класса, то это не виртуальная функция, а новый метод производного класса.
Завершая рассмотрение примера, еще раз подчеркнем, что при подмене виртуальной функции требуется полное совпадение сигнатур, имен и типов возвращаемых значений функций в базовом и производном классах.
Как уже было упомянуто, виртуальной функцией может быть только нестатическая компонентная функция. Виртуальной не может быть глобальная функция. Функция, подменяющая виртуальную, в производном классе может быть описана как со спецификатором virtual, так и без него. В обоих случаях она будет виртуальной, т.е. ее вызов возможен только для конкретного объекта. Виртуальная функция может быть объявлена дружественной (friend) в другом классе.
Механизм виртуального вызова может быть подавлен с помощью явного использования полного квалифицированного имени. Таким образом, при необходимости вызова из производного класса виртуального метода (компонентной функции) базового класса употребляется полное имя. Например,
struct base {
virtual int f(int j) { return j * j ; }
};
void print_(base* pb){pb->f(1);}
struct dir: public base {
int f(int i) { /* Ваш код*/ return base::f(i * 2); }
};
void main (){
dir D;
print_(&D);
}
В коде, где используется наследование часто применяется операция преобразования типа вида:
dynamic_cast<type-id> (expression)
где type-id должен быть указателем или ссылкой на ранее определенный тип класса или "указателем на void". Тип операнда expression должен быть указателем, если type-id является указателем, или l-значением, если type-id является ссылкой.
У преобразования типа dynamic_cast есть особенности. Так мы знаем, что указателю на базовый класс можно присвоить адрес производного класса (класса потомка) и это можно сделать как с помощью dynamic_cast так и без него, т.е. неявно. Например:
class B {virtual void f() { } };
class D : public B { virtual void f() { } };
B* pb = new D; или так
B* pb = dynamic_cast<B*>(new D);
Но обратную операцию, т.е. присвоить указателяю на производный класс адрес базавого класса неявно нельзя. Например, строка:
D* pb = new B;
выдаст сообщение об шибке при компиляции. Но с помощью операции явного преобразования типа эту ошибу можно подавить, например, так
B* b = new B; // new D
D* pb = dynamic_cast< D*>(b); или так
D* pb = static_cast< D*>(b); или так
D* pb = (D*)(b);
Во втором и третьем случае результат будет одинаков: в pb будет сохранён адрес объекта сласса В, но в первом случае в pb будет сохранён NULL. Так как dynamic_cast проверяет, на какой тип объекта указывает аргумент (expression) и если он выше (базовый класс стоит на вершине иерархии, а все производные от него размещаются ниже) по иерархии, чем type-id, то такое преобразование не считается безопасным и в pb записывается NULL. Но стоит только new B заменить на new D, как все три преобразования будут работать одинаково.
Абстрактные классы. Абстрактным классом называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция. Чистой виртуальной называется компонентная функция, которая имеет следующее определение:
virtual тип имя_функции(список_формальных_параметров) = 0;
В этой записи конструкция "= 0" называется "чистый спецификатор". Пример описания чистой виртуальной функции:
virtual void fpure(void) = 0;
Чистая виртуальная функция "ничего не делает" и недоступна для вызовов. Ее назначение - служить основой для подменяющих ее функций в производных классах. Исходя из этого становится понятной невозможность создания самостоятельных объектов абстрактного класса. Абстрактный класс может использоваться только в качестве базового для производных классов. При создании объектов такого производного класса в качестве подобъектов создаются объекты базового абстрактного класса. Предположим, что имеется абстрактный класс:
class В {
protected:
virtual void func(char)=0;
void sos (int) ;
};
//На основе класса в можно по-разному построить производные классы:
class D: public В{
void func(char){};
};
class E: public В {
void sos (int) ;
};
В классе d чистая виртуальная функция func () заменена конкретной виртуальной функцией того же типа. Функция B::sos() наследуется классом D и доступна в нем и в его методах. Класс D не абстрактный. В классе е переопределена функция B::sos(), а виртуальная функция в B::func() унаследована. Тем самым класс E становится абстрактным и может использоваться только как базовый.
Как всякий класс, абстрактный класс может иметь явно определенный конструктор. Из конструктора возможен вызов методов класса, но любые прямые или опосредованные обращения из конструктора к чистым виртуальным функциям приведут к ошибкам во время выполнения программы.
Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия обычно невозможно использовать непосредственно, но на их основе можно, как на базе, построить частные производные классы, пригодные для описания конкретных объектов.
Например, из абстрактного класса "фигура" можно сформировать класс "треугольник", "окружность" и т.д. Воспользуемся новыми возможностями и перепишем базовый класс Figura, используя чистые виртуальные функции.
// В свойствах проекта в опциях по C/С++ ->Библиотека времени выполнения; установить /MTd
//В свойствах проекта Компоновщик->Ввод->Дополнительные зависимости; вставить на первое место uafxcwd.lib
#include "stdafx.h"
#include "afxwin.h"
#include "iostream"
using namespace std ;
HWND hwnd = 0;
HDC hdc = 0;
void InitGraphic(){system("mode con cols=168 lines=55"); system("pause >> void");
hwnd=FindWindow(_T("ConsoleWindowClass"),_T("C:\\Windows\\system32\\cmd.exe"));hdc=GetWindowDC(hwnd);}
void CloseGraphic(){ReleaseDC(hwnd, hdc); CloseHandle(hwnd);}
//-------------------------------------------------------------------------- IFigure
class IFigure{ //интерфейсный класс
protected:
int fMove; //0 - фигура двигается; 1 - фигура мигает на месте; 2 - фигура стоит на месте;
int fClr; //0 - фигура цвет не менеят; 1 - фигура меняет цвет
public:
IFigure(): fMove(0), fClr(0){/*cout<<"\n IFigure()";*/}
virtual void show()=0;
virtual void hide()=0;
virtual void move(int x, int y)=0;
virtual ~IFigure(){}
};
//-------------------------------------------------------------------------Square
class Square:virtual public IFigure {
POINT pt[5];
COLORREF color;
public:
Square(POINT* p): color(RGB(255,0,0)){ for(int i =0 ; i <5; i++) pt[i] = p[i]; }
void SetColor(COLORREF cl){color = cl;}
void show(){
CPen pen(PS_SOLID,2,color);
SelectObject(hdc,pen);
Polyline(hdc,pt,5 );
}
void hide(){
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(0,0,0));
SelectObject(hdc,pen);
Polyline(hdc,pt,5 );
}
void move(int x, int y){
for(int i = 0; i<5;i++){ pt[i].x+=x;pt[i].y+=y;} }
virtual ~Square(){/*cout<<"\t ~Square()";*/}
};
//---------------------------------------------------------------------------ClsEllipse
class ClsEllipse: virtual public IFigure {
CPoint pt1,pt2;
public:
ClsEllipse():pt1(100,100),pt2(200,200) {}
virtual void show() {
CPen pen(PS_SOLID,2,RGB(0,255,0));
SelectObject(hdc,pen);
Arc(hdc,pt1.x,pt1.y,pt2.x,pt2.y,100,200,0,100);
}
virtual void hide() {
CPen pen(PS_SOLID,2,RGB(0,0,0));
SelectObject(hdc,pen);
Arc(hdc,pt1.x,pt1.y,pt2.x,pt2.y,100,200,0,100);
}
virtual void move(int x, int y) { pt1.x+=x,pt1.y+=y,pt2.x+=x,pt2.y+=y; }
virtual ~ClsEllipse(){/*cout<<"\t ~ClsEllipse()";*/}
};
//-------------------------------------------------------------------------Rectan
class Rectan: public IFigure {
Square* pSq;
public:
virtual void show(){pSq->show();}//Делегирование
virtual void move(int x, int y){pSq->move(x,y);}//Делегирование
virtual void hide(){pSq->hide();}//Делегирование
void SetColor(COLORREF cl){pSq->SetColor(cl);}
Rectan (Square& p){pSq = new Square(p);}
virtual ~Rectan(){delete pSq;}
};
//-------------------------------------------------------------------------DrowTxt
class DrowTxt{
CString str;
public:
DrowTxt(CString s):str(s){}
void show(){
CDC* pCDC = CDC::FromHandle(hdc);
pCDC->SetTextColor(RGB(255,0,0));
pCDC->SetBkColor(RGB(0,0,0));
pCDC->TextOutW(300,100,str); pCDC->TextOutW(0,0," ");
}
};
//-------------------------------------------------------------------------Heir
class Heir: public Square, public ClsEllipse{ //Виртуальный базовый класс
public: //(Множественное наследование)
Heir(POINT *p):Square(p),ClsEllipse(){/*cout<<"\t Heir()";*/ }
void show(){Square::show(); ClsEllipse::show();}
void move(int x, int y){Square::move(x,y); ClsEllipse::move(x,y);}
void hide(){Square::hide(); ClsEllipse::hide();}
virtual ~Heir(){/*cout<<"\n ~Heir()";*/}
};
//------------------------------------------------------------------------RecordPlayer
class RecordPlayer{ //Чтобы воспользоваться классом, объекты должны поддерживать интерфейс IFigure
IFigure**pFig;//Массив указателей IFigure*
int n; //Текущее количество указателей в массиве
int N; //Размерность массива
public:
void Insert(IFigure* pF){if (n<N) pFig[n++] =pF; }
RecordPlayer(int Nfig): N(Nfig), n(0) { pFig = new IFigure*[N]; }
virtual void show(){ for(int i = 0; i < n; i++) pFig[i]->show(); }//Полиморфизм
virtual void hide(){ for(int i = 0; i < n; i++) pFig[i]->hide(); }//Полиморфизм
virtual void move(int x, int y){ for(int i = 0; i < n; i++) pFig[i]->move(x,y); }//Полиморфизм
void PlayMyObject(int x, int y){ for(int i = 0; i <150 ; i++){show();Sleep(24);hide(); move(x,y);} show();}
virtual ~RecordPlayer(){delete []pFig;}
};
void main(){
POINT pt1[5];
pt1[0].x = 40;pt1[0].y=40;
pt1[1].x = 40;pt1[1].y=140;
pt1[2].x = 140;pt1[2].y=140;
pt1[3].x = 140;pt1[3].y=40;
pt1[4].x = 40;pt1[4].y=40;
InitGraphic();
DrowTxt dtxt("Привет");
dtxt.show();
getchar();
Heir hr(pt1);
for(int i = 0 ; i <100 ; i++){
hr.show(); Sleep(24); hr.hide(); hr.move(0,3);
}
getchar();
ClsEllipse elp;
Square sq1(pt1), sq2(pt1), sq3(pt1);
sq1.SetColor(RGB(255,255,0)); sq2.SetColor(RGB(0,255,0));
sq3.SetColor(RGB(0,0,255)); hr.SetColor(RGB(0,255,255));
sq2.move(20,20); sq3.move(40,30); hr.move(0,-150);
Rectan rec(sq3);
RecordPlayer RPlayer(5);
RPlayer.Insert(&elp);
RPlayer.Insert(&sq1);
RPlayer.Insert(&sq2);
RPlayer.Insert(&rec);
RPlayer.Insert(&hr);
RPlayer.PlayMyObject(3,0);
getchar();
CloseGraphic();
}
Дата добавления: 2020-12-11; просмотров: 356;