【C++】多态/虚函数/虚表指针,虚表

多态

多态分为

  1. 静态联编(早绑定)------编译时的多态:函数重载(名字粉碎技术)和运算符重载
  2. 动态联编(晚绑定)------运行时多态:简单来说,就是程序在运行前不能根据函数名和参数确定调用那个函数,必须在程序执行过程中,根据执行的具体情况来动态确定,它是通过类公有继承关系中的虚函数来实现的。目的也是为了建立一种通用程序,通用性是程序追求的目标之一。

虚函数

定义:虚函数是一个类的成员函数。
语法:virtual 返回类型 函数名参数列表()
关键virtual指明该成员为虚函数,仅用在类的定义中,如果虚函数在类外定义可以不加virtual

虚函数的特点:

当某一个类的成员函数被定义为虚函数,那么由该类派生出来的所有派生类,该函数都保持虚的特征。

class Animal
{
private:
    string name;
public:
    Animal(const string& na) : name(na) {}
public:
    virtual void eat() {}
    virtual void walk() {}
    virtual void PrintInfo() {}

    string& get_name() { return name; }
    const string& get_name()const { return name; }
};

class Dog : public Animal
{
private:
    string owner;
public:
    Dog(const string& ow, const string& na) : Animal(na), owner(ow) {}

    virtual void eat() { cout << "Dog eat: bone" << endl; }
    virtual void walk() { cout << "Dog walk: run" << endl; }
    virtual void PrintInfo()
    {
        cout << "Dog owner 's name: " << owner << endl;
        cout << "Dog name: " << get_name() << endl;
    }
};

class Cat : public Animal
{
private:
    string owner;
public:
    Cat(const string& ow, const string& na) : Animal(na), owner(ow) {}

    virtual void eat() { cout << "Cat eat: fish" << endl; }
    virtual void walk() { cout << "Cat walk: silent" << endl; }
    virtual void PrintInfo()
    {
        cout << "Cat owner 's name: " << owner << endl;
        cout << "Cat name: " << get_name() << endl;
    }
};
//如果派生类传递基类的引用或指针,再以基类指针调用虚函数
void fun(Animal& animal)
{          //指针*animal
    animal.eat();
    animal.walk();
    animal.PrintInfo();
}
int main(void)
{
    Dog dog("嘟嘟", "哈士奇");
    Cat cat("baiU", "喵酱");

    fun(dog);
    fun(cat);

    return 0;
} 

实现动态联编的注意点:

1.必须使用公有继承
2.类中的类方法必须是虚函数
3.要实现运行时的多态,必须用指针或者引用来调用虚函数。

为什么要是用公有继承?

原因:因为私有继承不代表是一个的意思)就是外部函数无法访问派生类的私有和保护属性。比如:猫是动物的一种。

使用虚函数的注意事项:

  1. 派生类中定义的虚函数必须和基类中定义的虚函数同名,同返回类型,同参数列表,否则会被认为是重载,而不是虚函数。
    如:基类返回,基类指针,派生类返回,派生类指针是允许的。
//这是函数的重载。
class Object
{
public:
virtual Object*fun() {}
};
class Base
{
  public:
  virtual Base*fun(){}
}
  1. 只有类的成员函数才能被说明为虚函数,因为虚函数仅适用于有继承关系的类对象。
  2. 静态成员,是所有同一类对象公有,不能作为虚函数。
  3. 内联函数不能作为虚函数,每个对象一个拷贝,没有映射关系。
  4. 实现动态多态性时,必须使用基类类型的,指针或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态多态。
    如:
void fun(Animal& animal)
{          //指针*animal
    animal.eat();  //&amimal就是基类引用
    animal.walk();
    animal.PrintInfo();
}
  1. 析构函数可以被定义为虚函数,构造函数不可以被定义为虚函数,因为调用构造函数时,对象还没有完成实例化(也就是对象没有内存空间,如何保存访问虚表的指针?)
    在基类和派生类中动态分配内存空间,必须把析构函数定义为虚函数,实现撤销对象时的多态性。
  2. 函数执行速度要稍慢一点,为了实现多态性,每一个派生类均要保存相应的虚函数的入口地址表,函数的调用机制是间接实现的,所以多态性总要付出一定的代价,但是通用性是一个更高的目标。
  3. 如果定义在类外,virtual只能加载函数声明前面,不能(再)再函数定义前面,正确的定义必须不包括virtual。

虚函数表,和虚表指针的概念

实际上,运行时的多态是因为虚函数表的存在,如果设计的类中有虚函数,那么在编译阶段就会生成虚函数指针和虚函数表,里面存放了各个虚函数的函数指针。
如果派生类重写了基类的虚函数,那么派生类的虚函数就覆盖虚函数表里的基类虚函数。

class Object
{
private:
    int value;
public:
    Object(int x = 0) :value(x) {}
    virtual void add() { cout << "object::add" << endl; }
    virtual void fun() { cout << "object::fun" << endl; }
};
class Base :public Object
{
private:
    int sum;
public:
    Base(int x = 0) :Object(x + 10), sum(x) {}
    virtual void add() { cout << "Base::add" << endl; }
    virtual void fun() { cout << "Base::fun" << endl; }
};
int main()
{
    Base base(10);
    Object* op = &base;
    return 0;
}

1.首先base调用构造函数,发现继承了Object,因此会先调用基类的构造函数,构造隐藏基类对象。
2.在构造时发现,基类中有虚函数,就会在隐藏基类对象中生成指向基类中虚函数的指针(vfptr)。如下图:
隐藏基类对象的虚表指针vfptr指向的基类的虚表

请添加图片描述

请添加图片描述
3.接下来返回Base类中,调用构造函数,构建base对象,发现Base类中也有虚函数。
4.接下来就会让隐藏基类对象中的vfptr指针指向派生类的虚函数表。(虚函数函数在数据区只有一份)
如下图:
隐藏基类对象的虚表指针指向派生类的虚表
如果重写基类的虚函数,就会覆盖基类的虚函数。
请添加图片描述

请添加图片描述

注意:(只有基类的对象中有虚表指针(只有一个))

  1. 如果使用"对象.函数名()",不管方法是否是虚函数,都调用该对象的方法。

  2. 如果使用基类的指针或者引用指向派生类,那么调用的虚方法时,会采用动态联编,调用派生类的虚方法。
    换句话说,基类指针和引用是通过基类的虚表指针查虚表的方式,实现动态绑定

Object* op = &base;

基类指针指向base对象,Base类中有隐藏基类对象和base对象两个成员,隐藏基类对象内存中的虚表指针,指向Base类中的虚表。
请添加图片描述

构造函数为什么不能作为虚函数?

  1. 存储角度,虚函数都对应一个指向vtable虚表的指针,这个指向虚表的指针实际上存储在对象的内存空间中,而对象是通过调用构造函数进行实例化后才有内存空间,那构造函数如果是虚函数,对象如何保存指向虚表的指针。
  2. 使用角度,虚函数通常都是通过父类的指针或者引用来调用子类的成员函数,而构造函数是在创建对象的时候自动调用。不可能通过父类的指针或者引用来调动

从汇编角度来看动态联编过程

int main()
{
    Base base(10);
    Object* op = &base;
    op->add();
    op->fun();
    op->printf();![请添加图片描述](https://img-blog.csdnimg.cn/cb13a15f698b417ead5a4ea206cd039f.png)

    return 0;
}

虚表:存放的是虚函数的指针。

请添加图片描述

 op->add(); 
  1. 先把op的地址放入eax,
  2. 再从eax中取出虚表的地址放入edx
  3. 再从edx中取出虚表的第一个地址,这个地址就是第一个虚函数的地址。
    请添加图片描述

执行这一步时:
请添加图片描述
会跳转到这:
请添加图片描述

 op->fun();

前两步操作和op->fun()相同,从edx中取地址,要加4个字节,取的是虚表中第二个函数的地址。
(edx+4),因为在虚表中存放的是虚函数指针。
请添加图片描述

三层继承时的虚表:

class Object
{
private:
    int value;
public:
    Object(int x = 0) : value(x) {}

    virtual void add() { cout << "Object::add" << endl; }
    virtual void fun() { cout << "Object::fun" << endl; }
    virtual void Print() { cout << "Object::Print" << endl; }
};

class Base : public Object
{
private:
    int sum;
public:
    Base(int x = 0) : Object(x + 10), sum(x) {}
    virtual void add() { cout << "Base::add" << endl; }
    virtual void fun() { cout << "Base::fun" << endl; }
    virtual void show() { cout << "Base::show" << endl; }
};
class test :public Base
{
private:
    int num;
public:
    test(int x = 0) : Base(x + 10), num(x) {}
    virtual void add() { cout << "Base::add" << endl; }
    virtual void fun() { cout << "Base::fun" << endl; }
    virtual void show() { cout << "Base::show" << endl; }
};

请添加图片描述
文字描述:
在Object类中可以看见三个虚函数:

 virtual void add() 
 virtual void fun() 
 virtual void Print() 

在Base中可以看到四个虚函数

virtual void add() 
virtual void fun() 
virtual void Print() 
virtual  void show()

在Test中可以看到四个虚函数

virtual void add() 
virtual void fun() //是继承Base的,Object的被覆盖了
virtual void Print() 
virtual  void show()

而虚表指针是在Object类的隐藏基类对象的内存空间中。指向的是Test类中的虚表。
创建基类指针:

int main()
{
Test t1(10); 
Object*op;
Base*bp;
Test*tp;
op = &t1;
bp =&t1;
tp = &t1;
}

请添加图片描述
请添加图片描述
均能访问到。

使用对象调用普通方法时产生的动态联编:

class Object
{
private:
	int value;
public:
	Object(int x = 0) : value(x) {}

	virtual void add() { cout << "Object::add" << endl; }
	virtual void fun() { cout << "Object::fun" << endl; }
	virtual void Print() { cout << "Object::Print" << endl; }

	void function()
	{
		fun();
	}
};
int main(void)
{
    Test t1;
    Base b1;
    Object obj;

    t1.function();
    b1.function();
    obj.function();

    return 0;
}

执行完:

 Test t1;
    Base b1;
    Object obj;

t1, b1, obj的虚表状态。

请添加图片描述

  1. 当执行 t1.function();,this指针里的虚表指针指向的是Test类中的虚表,查询虚表,调用虚函数fun(),如下图:
    请添加图片描述
    请添加图片描述

  2. 执行b1.function();和1一样。

  3. 请添加图片描述

  4. 执行obj.function()也和1一样。

  5. 请添加图片描述

结果
请添加图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值