c++虚函数详解(多态,虚表,联编,抽象类)

虚函数

1.多态

在这里插入图片描述

编译时的多态又叫早期绑定
//如
int max(int a,int b){ return a>b?a:b; }
double max(double a,double b){ return a>b?a:b; }
char max(char a,char b){ return a>b?a:b; }
int main()
{
    max(1,2);
    max(12.2,11,1);
    max('z','e');
    return 0;
}
运行时的多态又叫晚绑定

需要用到虚函数

//如
#define PI 3.1415
class point
{
private:
   float x;
   float y;
public:
   point(float x=0,float y=0) : x{x},y{y} { }
   virtual void Area()const = 0;
};
class circle  :public point
{
private:
  float r;
public:
  circle(float r=1,float x=0,float y=0) : point(x,y),r{r}  { }
  virtual void Area()const{
    cout<<"Cirlce Area: "<<r*r*PI<<endl;
  }
};
void fun(point& p)
{
   p.Area();
}
int main()
{
   circle a;
   fun(a);
   return 0;
}

注意运行时的多态性: 公有继承 + 虚函数 + (指针或引用调用虚函数)。

2.定义虚函数的规则

  1. 派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型(三同)。否则被认为是 同名覆盖,不具有多态性。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个 例外(协变)。

  2. 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。友元函数和 全局函数也不能作为虚函数。

  3. 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。

  4. 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。

  5. 构造函数和拷贝构造函数不能作为虚函数。构造函数和拷贝构造函数是设置虚表指针。

  6. 析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例 化(虚表指针没有设置)。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义 为虚函数,实现撤消对象时的多态性。

  7. 实现运行时的多态性,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的 对象,并通过该指针指向虚函数,才能实现运行时的多态性。

  8. 在运行时的多态,函数执行速度要稍慢一些:为了实现多态性,每一个派生类中均要保存相应虚函 数的入口地址表,函数的调用机制也是间接实现。所以多态性总是要付出一定代价, 但通用性是一个 更高的目标。

  9. 如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。正确的定义必 须不包括virtual。

    eg:

    //协变示例->基类中返回基类指针,派生类中返回派生类指针
    class base
    {
    	int a = 1;
    public:
    	virtual const base* getobj() const { 
            cout <<"base::a :"<< a << endl;
            return this; 
        }
    };
    class object : public base
    {
    	int b = 12;
    public:
    	virtual const object*  getobj() const {
        cout << "object::b :" << b << endl; 
        return this; 
        }
    };
    void fun(base* p)
    {
    	cout << p->getobj()<< endl;
    }
    int main()
    {
    	base a;
    	object b;
    	fun(&a);
    	fun(&b);
    	return 0;
    }
    

    运行结果:

在这里插入图片描述

3.运行时的多态的原理(过程)

代码在预编译阶段扫描到有关键字 virtual 修饰的函数,则会为每个类创建虚函数指针表简称虚表,虚表里面存储了当前类的虚函数地址。每个对象都有虚表指针,当进入该类的构造函数中,构造函数中,会首先设置虚表指针指向该类的虚表,当进入析构函数时,首先将虚表指针重新指向该类的虚表。
即 构造函数设置虚表指针,析构函数重置虚表指针。

重点:派生类有n个父类,就有n个虚表,其对象就有n个虚表指针。

  1. 单一继承
//单一继承的原理图解
#define PI 3.1415
class point
{
private:
	float x;
	float y;
public:
	point(float x = 0, float y = 0) : x{ x }, y{ y } { }
	virtual void Area() = 0;
	virtual void show() { cout << "point:: " << endl; }
};
class circle: public point
{
private:
	float r;
public:
	circle(float r = 1, float x = 0, float y = 0) : point(x, y), r{ r }  { }
	virtual void Area() {
		cout << "Cirlce Area: " << r * r * PI << endl;
	}
	virtual void print() { cout << "hello world! " << endl; }
};

int main()
{
   point* a;
   circle b;
   a = &b;
   return 0;
}

1.预编译阶段创建各类的虚表(vftable)

派生类首先继承基类虚表,然后将自己新增的虚函数,重写基类的虚函数更新到虚表中。

指向type_info的指针:

每一个虚函数表前都有一个指针指向type_info,负责对RTTI(runtime type interpret)的支持。

基类构造函数

在这里插入图片描述

2.运行阶段
进入circle的构造函数,跳转到基类 point的构造函数,然后将虚表指针vfptr指向 point::vftable,
初始化 x,y;回到circle 构造函数,设置虚表指针vfptr指向 circle::vftable ,构初始化 r;

如下图:

3当使用基类指针或引用调动虚函数时,会对此时虚表指针指向的虚表查找,从而实现动态链接(多态)。
  1. 多重继承
class Base1
{
 private:
    int num1;
 public:
    Base1(int x = 0) : num1(x){}
    virtual void fun(){}
    virtual void fun1(){}
    virtual ~Base1(){}
};
class Base2
{
 private:
    int num2;
 public:
    Base2(int x = 1) : num2(x){}
    virtual void fun(){}
    virtual void fun2(){}
    virtual ~Base2(){}
};
class Object : public Base1,public Base2
{
 private:
 	int val;
 public:
	Object(int x = 5) : Base1(x+10),Base2(x+5),val(x) {}
	virtual void fun(){}
    virtual void fun1(){}
    virtual void fun2(){}
    virtual ~Objecct(){}
}
int main()
{
    Object obj;
    return 0;
}

知识点:派生类先继承谁,它新添加的虚函数就加入从它那继承来的虚表中

图解:

4.联编

  • C++语言中,使用类类型的引用或指针调用虚函数(成员选择符“->”),则程序在运行时选择虚函
    数的过程,称为动态联编.
  • C++语言中,使用对象名加点“.”成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译
    和链接时确定。(称为静态联编)。
  • 函数重载和函数模板也是静态联编。
  • 5.纯虚函数和抽象类

纯虚函数的概念:

纯虚函数(pure virtual function)是指没有具体实现的虚成员函数。它用于这样的情况:设计一 个类型时,会遇到无法定义类型中虚函数的具体实现,其实现依赖于不同的派生类。 定义纯虚函数的一般格式为: virtual 返回类型 函数名(参数表)= 0;

“=0”表明程序员将不定义该虚函数实现,没有函数体,只有函数的声明; 函数的声明是为了在虚函 数表中保留一个位置。“=0”本质上是将指向函数体的指针定义为nullptr。

抽象类的概念:

含有纯虚函数的类是抽象类。

抽象类的使用规则:

(1)抽象类只能用作其他类的基类,不能创建抽象类的对象。
(2)抽象类不能用作参数类型、函数返回类型或显式类型转换。
(3)可以定义抽象类的指针和引用,此指针可以指向(引用可以引用)它的派生类的对象,从而实现
运行时多态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值