》多态定义的概念是:当不同的对象收到相同的消息时做出了不同工作这种现象就叫做多态。

  》那么在C++中是如何实现多态的呢?

  首先多态分为编译时多态和运行时多态。在这里还有一个连编的概念,连编即把函数名和函数体中的代码连接在一起的过程。静态连编就是在编译阶段完成的连编,编译时多态就是通过静态连编完成的。静态连编时,系统通过实参与形参进行匹配,对于同名的重载函数就根据参数上的差异进行区分,然后进行连编,从而实现多态性。运行时多态使用动态连编实现的,动态连编是指运行阶段完成的连编。即当程序调用某一个函数名时,才去寻找和连接其相对应的程序代码。

 在C++中,编译时多态性主要是通过函数重载和运算符重载实现的,运行时多态性主要是通过虚函数来实现的。

虚函数的引入:

  

class Base
{
public:
Base(int x,int y)
:a(x)
,b(y)
{}
void show()
{
cout << "Base----" << endl;
cout << a << " " << b << endl;
}
private:
int a;
int b;
};
class Deriver :public Base
{
public:
Deriver(int x, int y, int z)
:c(z)
,Base(x,y)
{}
void show()
{
cout << "Deriver----" << endl;
cout << c << endl;
}
private:
int c;
};
void main()
{
Base mb(60, 60), *pc;
Deriver mc(10, 20, 30);
pc = &mb;
pc->show();
pc = &mc;
pc->show();
}

结果如下:

  wKiom1cR__myTrsxAAA0xqMJvr4925.png

   并不是我们所预想的,这说明,不管指针pc当前指向哪个对象(基类的或派生类的),“pc->show()”调用的都是基类定义的show()函数。

》虚函数的作用和定义:

  虚函数首先是基类的成员函数,但这个成员函数前面加上关键字virtual.并在派生类中被重载,就能实现动态调用的功能。

class Base
{
public:
Base(int x,int y)
:a(x)
,b(y)
{}
virtual void show()
{
cout << "Base----" << endl;
cout << a << " " << b << endl;
}
private:
int a;
int b;
};
class Deriver :public Base
{
public:
Deriver(int x, int y, int z)
:c(z)
,Base(x,y)
{}
void show()
{
cout << "Deriver----" << endl;
cout << c << endl;
}
private:
int c;
};
void main()
{
Base mb(60, 60), *pc;
Deriver mc(10, 20, 30);
pc = &mb;
pc->show();
pc = &mc;
pc->show();
}

结果:

wKiom1cSAXbS1wQvAAA1ayfxQyk260.png 

  为什么把基类中的函数show()定义为虚函数时,程序的运行结果就正确了呢?这是因为,关键字virtual指示C++编译器,函数调用“pc->show()”时,要在运行时确定所要调用的函数即要对该调用进行动态连编。我们把使用同一种调用形式“mp->show()”,调用同一类族中不同类的虚函数称为动态的多态性,即运行时多态性。

》虚函数的定义

virtual 返回类型 函数名(形参表)

{   函数体

}


》虚析构函数

class Base
{
public:
~Base()
{
cout << "调用基类Base的析构函数" << endl;
}
};
class Deriver:public Base
{public:
~Deriver()
{
cout << "调用派生类Deriver的析构函数" << endl;
}
};
int main()
{
Base* p;
p = new Deriver;
delete p;
return 0;
}

结果:

   wKioL1cTd8jQPM-UAAAuvVIRwJQ696.png

   这里创建了一个派生类的无名对象用基类的指针指向他,在delete指针时只调用了基类的析构函数没有调用派生类的析构函数原因是当撤销指针p所指的派生类的无名对象,而调用析构函数时,采用了静态连编方式,只调用了基类的析构函数。

   如果希望程序执行采用动态连编方式,可以将基类的析构函数声明为虚析构函数。

  virtual ~类名()

 {函数体

 };

  虽然基类和派生类析构函数的名字不同,但是如果将基类析构函数定义为虚函数,基类派生的所有派生类析构函数也都自动声明为虚析构函数。

class Base
{
public:
virtual ~Base()
{
cout << "调用基类Base的析构函数" << endl;
}
};
class Deriver:public Base
{public:
~Deriver()
{
cout << "调用派生类Deriver的析构函数" << endl;
}
};
int main()
{
Base* p;
p = new Deriver;
delete p;
return 0;
}

结果:

wKioL1cTonOw8ruCAAA26rAWZLI412.png

   当基类析构函数声明为虚析构函数时结果就符合我们所期望的,这是由于virtual关键字要求delete p时进行动态连编,先调用派生类析构函数再调用基类析构函数实现动态运行时多态。


》多继承与虚函数

   C++语言中,每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表),表中的每一个元素都指向一个虚函数的地址。(注意:虚表是从属于类的)

   此外,编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。

虚表的内容是依据类中的虚函数声明次序--填入函数指针。派生类别会继承基础类别的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。


》纯虚函数与抽象类

 声明 virtual 函数类型 函数名(参数表)=0;

 含有纯虚函数的类称为抽象类。

 那么纯虚函数存在的作用是什么呢?

纯虚函数只是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行重新定义。纯虚函数没有函数体,它的后边“=0”并不表示函数的返回值为0,他只是起形式上的作用,告诉编译系统“这是纯虚函数”,纯虚函数不具备函数的功能,不能被调用。