更多参考其他文档菜鸟文档W3C微软C++文档

虚函数和多态

虚函数

  • 在类的定义中,前面有virtual关键字的成员函数就是虚函数

    class base
    {
    virtual int get();
    };

    int base::get(){

    }
  • virtual 关键字值用在类定义里函数声明中,在类外写函数体时不用写

  • 参与多态

  • 构造函数和静态成员函数不能是虚函数

多态

表现形式 一

  • 派生类的指针可以赋给基类指针
  • 通过基类指针调用基类和派生类中的同名虚函数时:
    • 若该指针指向一个基类的对象,那么被调用是基类的虚函数;
    • 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数;
class CBase
{
public:
virtual void SomeVirtualFunction(){} //虚函数
};
class CDerived:public CBsae{
public:
virtual void SomeVirtualFunction(){}
//虚函数,virtual可以不写,但是写了对写子孙类等的人有好处,代码更明了
};
int main()
{
CDerived ODerived;
CBase * p = &ODerived;//p指针指向派生类
p->SomeVirtualFunction(); //多态性
//调用取决于指针指向的类的虚函数,故这里运用派生类的虚函数
return 0;
}

表现形式 二

  • 派生类的对象可以赋给基类引用
  • 通过基类引用调用基类和派生类中的同名虚函数时
    • 若该引用引用的是一个基类的对象,那么被调用是基类的虚函数
    • 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数
#include<iostream>

using namespace std;

class CBase
{
public:
virtual void SomeVirtualFunction(){} //虚函数
};
class CDerived:public CBase
{
public:
virtual void SomeVirtualFunction(){} //虚函数
};
int main()
{
CDerived ODerived;
CBase &r = ODerived; //这里引用的是 派生类
r.SomeVirtualFunction();
// 这里调用取决于 r引用的类是基类还是派生类
return 0;
}

多态的作用

  • 在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加代码较少

多态实例

  • 游戏程序多态
#include<iostream>

using namespace std;

//表现多态的游戏实例
//非多态

class CCreature{

protected:
int nPower; //代表攻击力
int nLifrValue; //代表生命值
};

class CDragon:public CCreature{
public:
void Attack(CWolf * pWolf) //攻击狼
{
//表现攻击动作的代码
pWolf->Hurted(nPower);
pWolf->FightBack(this); //this指向攻击的发起者
}
void Attack(CGhost * pGhost)//攻击鬼
{
//表现攻击动作的代码
pWolf->Hurted(nPower);
pWolf->FightBack(this); //this指向攻击的发起者
}
//有多少种怪物就要有多少个Attack函数,相应的还有FightBack函数


void Hurted(int nPower)
{
//表现受伤的代码
nLifrValue -= nPower;
}
void FightBack(CWolf * pWolf)
{
//表现反击动作的代码
pWolf->Hurted(nPower / 2);
}
void FightBack(CGhost *pGhost)
{
//表现反击动作的代码
pGhost->Hurted(nPower / 2);
}
};

//如果想要新增一个怪物,此时所有类需要增加两个成员函数
void Attack(CA * pA)
void FightBack(CA * pA)
  • 多态形的写法
#include <iostream>

using namespace std;

//表现多态的游戏实例
//多态

class CCreature //基类
{

protected:
int m_nPower; //代表攻击力
int m_nLifrValue; //代表生命值
public:
virtual void Attack(CCreature *pCreature) {}
virtual void Hurted(int nPower) {}
virtual void FightBack(CCreature *pCreature) {}
//基类只有一个Attack成员函数和FightBack成员函数,并且是虚函数
};

class CDragon: public CCreature //派生类 龙
{
virtual void Attack(CCreature *pCreature) {}
virtual void Hurted(int nPower) {}
virtual void FightBack(CCreature *pCreature) {}
};

void CDragon::Attack(CCreature *p)
{
//表现攻击动作的代码
p->Hurted(m_nPower); //多态
p->FightBack(this);
}
void CDragon::Hurted(int nPower)
{
//表现受伤动作的代码
m_nLifrValue -= nPower;
}

void CDragon::FightBack(CCreature *p)
{
//表现反击动作的代码
p->Hurted(m_nPower / 2); //多态
}

几何形体处理程序

  • 例子
#include <iostream>

using namespace std;

//几何形体处理程序


//类的声明----------------------
class CShape //图形基类
{
public:
virtual double Area() = 0; //纯虚函数 ,没有实体
virtual void PrintInfo() = 0;
};
class CRectangle : public CShape //矩形
{
public:
int w, h;
virtual double Area();
virtual void PrintInfo();
};

double CRectangle::Area()
{
return w * h;
}

void CRectangle::PrintInfo()
{
cout << "Rectangle:" << Area() << endl;
}

class CCircle : public CShape
{ //圆
public:
int r;
virtual double Area();
virtual void PrintInfo();
};

double CCircle::Area()
{
return 3.14 * r * r;
}

void CCircle::PrintInfo()
{
cout << "Circle:" << Area() << endl;
}

class CTriangle : public CShape
{ //三角
public:
int a, b, c;
virtual double Area();
virtual void PrintInfo();
};

double CTriangle::Area()
{
double p = (a + b + c) / 2.0;
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 mian()
{
int i;
int n;
CRectangle *pr;
CCircle *pc;
CTriangle *pt;
cin >> n; //几何图形的个数
for (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;

default:
break;
}
}
qsort(pShapes, n, sizeof(CShape *), MyCompare); //这里排序算法没有写明

for (i = 0; i < n; i++)
{
pShapes[i]->PrintInfo();
return 0;
}
return 0;
}

int MyCompare(const void *s1, const void *s2) //传入的是数组中的图形元素指针
{
double a1, a2;
CShape **p1;
CShape **p2;
p1 = (CShape **)s1;
//数组中的元素是CShape指针,这里要调取CShape指针指向图形的元素
//所以要指向指针数组,故写成指向指针的指针,因此转换为CShape **
p2 = (CShape **)s2;
a1 = (*p1)->Area(); //将数组指针指向图形的面积赋值给a1
a2 = (*p2)->Area(); //将数组指针指向图形的面积赋值给a2
if(a1 < a2) //判断大小
return -1;
else if(a2 <a1)
return 1;
else
return 0;
}
  • 另外,在不是构造、析构函数的成员函数调用虚函数就是多态!!

构造函数和析构函数中调用虚函数

  • 不是多态,编译时就确定了调用的是自己的类或基类中定义的函数,不会到运行时才决定调用的是自己的还是派生类的函数
  • 程序运行时先调用的是基类的构造函数 ,此时派生类还没有构造,易出错,所以不能调用派生类中的函数,所以就不是多态
  • 在派生类中有与基类中同名同参数的函数,不加virtual也自动成为虚函数

多态的实现原理

  • 虚函数表,在类中定义虚函数时就存在

  • 占用内存、查找需要消耗时间

  • 引入

    #include <iostream>

    using namespace std;

    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()
    {

    cout << sizeof(Base) << "," << sizeof(Derived);
    return 0;
    }

    //输出:
    16,16 //这是因为有虚函数表的存在,多分配了内存
  • 带有虚函数的类的对象占用的空间会明显较多

  • 时间成本增多,因为需要查询虚函数表

//实例
#include <iostream>

using namespace std;

class A
{
public:
virtual void Func() { cout << "A::Func" << endl; }
};

class B:public A
{
public:
virtual void Func() { cout << "B::Func" << endl; }
};

int main()
{
A a;
A *pa = new B(); //指向了一个B类的对象
pa->Func();
//64程序指针为8个字节
long long *p1 = (long long *)&a; //指向了a的虚函数表
long long *p2 = (long long *)pa; // 指向了pa指向的B类对象的虚函数表
*p2 = *p1; //使指向的B类对象的虚函数表的指针p2指向类A的对象a的虚函数表
pa->Func(); //这里pa本ying
return 0;
}

//输出为
B::Func
A::Func //这里虚函数表替换了,所以输出A的虚函数表

虚析构函数

  • 通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数
    • 但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数
  • 解决办法:把基类的析构函数声明为virtual
    • 派生类的析构函数可以virtual不进行声明
    • 通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数
  • 类如果定义了虚函数,则应该将析构函数也定义成虚函数
  • 一个类如果打算作为基类使用,也应该将析构函数定义为虚函数
  • 虚函数不能作为构造函数
#include <iostream>
using namespace std;

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;
}
//输出: bye from son
//此时虚构不完整


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;
}
//输出
bye from grandson
bye from son

纯虚函数和抽象类

  • 纯虚函数:没有函数体
virtual void Print() = 0; 
  • 抽象类:包含纯虚函数的类

    • 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象

    • 抽象类的指针和引用可以指向由抽象类派生出来的类的对象

      A a; //错,A是抽象类,不能创建对象
      A * pa; //ok,可以定义抽象类的指针和引用
      pa = new A; //错误,A是抽象类,不能创建对象
  • 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数。

  • 如果一个类从抽象类派生类而来,那么当且仅当它实现了基类中的所有虚函数,它才能成为非抽象类。

class A{  //抽象类
//抽象类里可以有成员变量、函数
public:
virtual void f() = 0;//纯虚函数
void g(){this->f()};//ok,class A为抽象类,不能有对象,所有此时this指针指向的是派生类对象 B
A(){
//f(); //错误 ,调用的是自己的f
}//在构造函数、析构函数内调用虚函数不是多态
};
class B:public A
{
public:
void f(){cout << "B:f()"<<endl;}
} ;
int main()
{
B b;
b.g();
return 0;
}