多态与虚函数
在类的定义中,前面有virtual定义的即为虚函数
virtual关键字只用在定义类的函数声明时,写具体函数时不用。
class Base{
private:
public:
virtual int get(){}
};
int base::get() {
}
对于派生类的对象可以赋值给基类,或者基类可以引用派生类。
如果基类指针或者引用指向基类对象,那么调用基类虚函数
如果基类指针或者引用指向派生对象,那么调用派生类对象
class A{
private:
public:
virtual void Print() { cout << "A::print" << endl; }
};
class B:public A{
private:
public:
virtual void Print() { cout << "B::print" << endl; }
};
class D :public A {
private:
public:
virtual void Print() { cout << "D::print" << endl; }
};
class E :public B {
private:
public:
virtual void Print() { cout << "E::print" << endl; }
};
int main() {
A a; B b; E e; D d;
A * pa = &a; B * pb = &b;
D * pd = &d; E * pe = &e;
pa->Print(); // a.Print()被调用,输出:A::Print
pa = pb;
pa->Print(); //b.Print()被调用,输出:B::Print
pa = pd;
pa->Print(); //d. Print ()被调用,输出:D::Print
pa = pe;
pa->Print(); //e.Print () 被调用,输出:E::Print
return 0;
}
游戏编程实例
- 非多态的实现方法
class CCreature{
private:
int nPower; int nLifeValue;
public:
};
class CDragon:public CCreature{
private:
public:
void Attack(CWolf *pWolf) {
//表现攻击动作代码
pWolf->Hurted(nPower);
pWolf->FightBack(this);
}
void Attack(CGhost *pGhost) {
//表现攻击动作代码
pWolf->Hurted(nPower);
pWolf->FightBack(this);
}
void Hurted(int nPower) {
//表现受伤动作代码
nLifeValue -= nPower;
}
void FightBack(CWolf *pWolf) {
pWolf->Hurted(nPower / 2);
}
void FightBack(CGhost *pGhost) {
pGhost->Hurted(nPower / 2);
}
};
此时,n种生物,CDragon中会有n个Attack函数…..,其他类也如此。
- 多态的实现方法
class CCreature{
private:
int m_nPower; int m_nLifeValue;
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FightBack(CCreature *pCreature);
};
class CDragon:public CCreature{
private:
public:
virtual void Attack(CCreature *pCreature);
virtual void Hurted(int nPower);
virtual void FightBack(CCreature *pCreature);
};
void CDragon::Attack(CCreature *pCreature) {
//表现攻击动作代码
pCreature->FightBack(this);
pCreature->Hurted(m_nPower);
}
void CDragon::Hurted(int nPower) {
//表现受伤代码
m_nLifeValue -= nPower;
}
void CDragon::FightBack(CCreature *pCreature) {
//表现反击动作代码
pCreature->Hurted(m_nPower / 2);
}
更多实例
- 几何形体处理程序
输入: n 个数目,即n行,每行以c开头
c:R-矩形-后面接长和宽
c:C-圆形-后面接半径
c:T-三角形-后面接三条边
输出:
从小到大输出几何形体的种类和面积
class CShape
{
private:
public:
virtual double Area() = 0;//纯虚函数
virtual void PrintInfo() = 0;//纯虚函数
};
class CRectangle:public CShape{
private:
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape{
private:
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle:public CShape{
private:
public:
int a, b, c;
virtual double Area();
virtual void PrintInfo();
};
double CRectangle::Area() {
return w*h;
}
void CRectangle::PrintInfo() {
cout << "Rectangle:" << Area() << endl;
}
double CCircle::Area() {
return 3.14*r*r;
}
void CCircle::PrintInfo() {
cout << "Circle:" << Area() << endl;
}
double CTriangle::Area() {
double p = (a + b + c) / 2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
void CTriangle::PrintInfo() {
cout << "Triangle:" << Area() << endl;
}
CShape *pShapes[100];
int MyCompare(const void *s1, const void *s2);
int main() {
int i, n;
CRectangle *pr; CTriangle *pt; CCircle *pc;
cin >> n;
for (int i = 0; i <n;i++) {
char c;
cin >> c;
switch (c){
case 'R':
pr = new CRectangle();
cin >> pr->w >> pr->h;
pShapes[i] = pr;
break;
case 'C':
pc = new CCircle();
cin >> pc->r;
pShapes[i] = pc;
break;
case 'T':
pt = new CTriangle();
cin >> pt->a >> pt->b >> pt->c;
pShapes[i] = pt;
break;
}
}
qsort(pShapes, n, sizeof(CShape*), MyCompare);
for (int i = 0; i < n;i++) {
pShapes[i]->PrintInfo();
}
return 0;
}
int MyCompare(const void*s1,const void*s2){
double a1,a2;
CShape **p1;//s1,s2是 void*,不可写“*s1”来取得s1指向的内容
CShape **p2;
//s1,s2是指向 pShapes 数组中的元素,元素类型是CShape*,即 s1,s2是指向指针的指针
p1 = (CShape**)s1;
p2 = (CShape**)s2;
//那么想取得他们所指向的内容只能.*p1
a1 = (*p1)->Area();
a2 = (*p2)->Area();
if (a1<a2) {
return -1;
}else if (a2<a1){
return 1;
}else{
return 0;
}
}
- 2
class Base {
public:
void fun1() { fun2(); }
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = &d;
pBase->fun1();
return 0;
}
结果
可以见下一段代码:
class Base {
public:
void fun1() { fun2(); }
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
Base d;
Base * pBase = &d;
pBase->fun1();
return 0;
}
再见一段代码
class Base {
public:
void fun1() { this->fun2(); } //this是基类指针,fun2是虚函数,所以是多态
virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived :public Base {
public:
virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
int main() {
Derived d;
Base * pBase = &d;
pBase->fun1();
return 0;
}
总结:在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或基类中定义的函数,不会等到运行时候才决定调用自己的还是派生的。
小quiz
class myclass {
public:
virtual void hello() { cout << "hello from myclass" << endl; };
virtual void bye() { cout << "bye from myclass" << endl; }
};
class son :public myclass {
public:
void hello() { cout << "hello from son" << endl; };
son() { hello(); };
~son() { bye(); };
};
class grandson :public son {
public:
void hello() { cout << "hello from grandson" << endl; };
void bye() { cout << "bye from grandson" << endl; }
grandson() { cout << "constructing grandson" << endl; };
~grandson() { cout << "destructing grandson" << endl; };
};
int main() {
grandson gson;
son *pson;
pson = &gson;
pson->hello(); //多态
return 0;
}
解释:从一开始定义grandson类型 gson,就要从顶端开始执行构造函数。好,myclass调用默认构造函数,对于son,则调用hello()函数,注意,此时不是多态,因为是在构造函数里面,所以生成 “hello from son”,接着调用grandson的构造函数,生成“constructing grandson”;接着定义son指针,并且让pson指向gson,所以到pson->hello()的时候,打印出”hello from grandson”,此时是多态哦。最终调用各自析构函数,由底到顶。
多态实现原理
class Base {
public:
int i;
virtual void Print() { cout << "Base:Print"; }
};
class Derived : public Base {
public:
int n;
virtual void Print() { cout << "Drived:Print" << endl; }
};
int main() {
Derived d;
cout << sizeof(Base) << "," << sizeof(Derived) << endl; ;
return 0;
}
这里写一下自己的理解,base里面含有int类型,大小是4个字节,因为每一个有虚函数的类(或者由虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都存放着虚函数表的指针,所以多出来的4个字节就是存放虚函数表的地址的。
一开始我在想,为什么函数不占字节呢,其实,如果在基类中加入函数也是不占据字节的,不信,请看:
class Base {
public:
int i;
void print2() { cout << "Base:Print-1"; }
virtual void Print() { cout << "Base:Print"; }
};
class Derived : public Base {
public:
int n;
virtual void Print() { cout << "Drived:Print" << endl; }
};
int main() {
Derived d;
cout << sizeof(Base) << "," << sizeof(Derived) << endl; ;
return 0;
}
因此,对于多态的原理,用以下两张图片即可解释:
即:多态的函数调用语句被编译成一系列根据基类指针所指向的(或者基类引用的)对象中存放的虚函数的地址,在虚函数表中查找虚函数地址,并调用相关指令。
虚析构函数
class CSon {
public: ~CSon() { };
};
class CGrandson :public CSon {
public: ~CGrandson() { };
};
int main() {
CSon *p = new CGrandson();
delete p;
return 0;
}
通过基类的指针删除派生类对象,只会调用基类的析构函数,这样会导致内存未被完全清除
因此,我们希望能够先调用派生类的析构函数,再调用基类析构函数(不可以将虚函数作为构造函数)
- 1
class son{
public:
~son() { cout<<"bye from son"<<endl; };
};
class grandson : public son{
public:
~grandson(){ cout<<"bye from grandson"<<endl; };
};
int main(){
son *pson;
pson=new grandson;
delete pson;
return 0;
}
- 2
class son {
public:
virtual ~son() { cout << "bye from son" << endl; };
};
class grandson : public son {
public:
~grandson() { cout << "bye from grandson" << endl; };
};
int main() {
son *pson;
pson = new grandson;
delete pson;
return 0;
}
纯虚函数和抽象类
- 纯虚函数
纯虚函数:没有函数体的虚函数
class A {
private:
int a;
public:
virtual void Print() = 0; //纯虚函数
void fun() { cout << “fun”; }
};
- 抽象类
定义:包含纯虚函数的类
1、只能作为基类来派生新类使用
2、不能创建抽象类的对象
3、抽象类可以进行指针和引用–>由抽象类派生出来的对象
A a; // 错, A 是抽象类, 不能创建对象
A * pa; // ok, 可以定义抽象类的指针和引用
pa = new A; //错误, A 是抽象类, 不能创建对象
- 纯虚函数和抽象类
抽象类中
- 成员函数可以调用纯虚函数
- 构造/析构函数不能调用纯虚函数
如果一个类从抽象类派生而来,只有当他实现基类中所有纯虚函数,才能成为非抽象类。
实现—诸如添加一个空括号等
class A {
public:
virtual void f() = 0; //纯虚函数
void g() { this->f(); } //ok
A() { } //f( ); // 错误
};
class B : public A {
public:
void f() { cout << "B: f()" << endl; }
};
int main() {
B b;
b.g();
return 0;
}