虚函数及虚函数表
首先,我们要分清三大概念:重载、重写(覆盖)和重定义
一. 函数重载
(1)在相同的作用域内(无继承关系,只在一个类内进行声明)
(2)进行多个函数声明
(3)多个函数的函数名相同,参数列表不同(可以是类型不同、参数类型不同、传参顺序不同)
(4)函数的返回值类型可以相同,可以不同。不能仅依靠函数返回值类型来判断该函数是否为重载。
二. 重写(覆盖)
(1)在不同的作用域内进行函数声明(存在于继承关系下,分别在基类和派生类中进行声明)
(2)能发生重写的函数被定义成虚函数
(3)重写发生在继承关系中,必须将基类函数前加上virtual关键字,将其声明为虚函数。派生类函数可加可不加。
(4)基类和派生类中发生重写的函数必须一模一样(函数返回值类型、函数名、参数列表中的参数类型、参数名、传参
顺序都必须相同)。
三. 重定义(隐藏)
(1)在不同的作用域内进行函数声明(存在于继承关系下,分别在基类和派生类中进行声明)
(2)函数名相同
(3)函数返回值类型可以相同可以不同
(4)在继承关系中,同名函数只要不构成重写,那就构成重定义(隐藏)
参数列表相同时,基类函数有virtual,构成重写;无virtual,构成重定义。
参数列表不同时,无论基类函数有无virtual,构成的都是重定义(隐藏)。
浅谈多态
多态可以简单地理解成“一个接口,多种方法”的实现。利用C++的多态性,使得程序在运行时才确定调用的函数,实现
了接口重用。也就是说,无论传递过来的是哪一个类的对象,函数都可以通过同一个接口调用到适用于不同对象的函数。
在C++中,多态性是依靠虚函数来实现的。在继承关系中,将基类函数定义为虚函数,在子类中声明和基类虚函数完全
相同的函数,此时,发生函数的重写。在虚函数表中修改函数的地址,实现“一个接口,多种方法”。
最常用的使用方法就是声明一个基类对象的指针,并使其指向一个子类对象的地址,通过虚函数的多态性调用子类对象
的函数,可以根据子类对象的不同而实现不同方法的调用。如果不使用虚函数,基类指针指向子类对象时只能调用子类对象
继承基类而得的数据,而不能访问到子类对象中新增的成员。
总结:
多态的使用通过虚函数的重写实现。将继承关系下的基类函数成员声明为虚函数,子类中与基类虚函数相同的函数会在
子类的虚函数表中重写(覆盖)基类函数的地址。通过常用方法(将基类对象指针指向子类对象),实现多态下的“一个接口,
多个方法“。
多态性通过虚函数实现,虚函数存在于继承关系下,下面,我们分别分析单继承和多继承下的多态使用
单继承
#include<iostream>
#include<Windows.h>
using namespace std;
class person{
public:
virtual void fun1()
{
cout << "person::function" << endl;
}
virtual void fun2()
{
}
};
class student:public person{
public:
virtual void fun1()
{
cout << "student::function" << endl;
}
virtual void fun3()
{}
};
int main()
{
person p1;
student s;
person *p = &s;
p->fun1();
s.fun1();
p1.fun1();
ststem("pause");
return 0;
}运行结果:结构示意图:
分析运行结果:
在单继承中,student类继承了person类,student类中声明fun1为虚函数,在派生类中存在与fun1相同的函数,构成重写。
重写时,把基类的虚函数表的虚函数person::fun1()地址改写成子类虚函数student::fun1()的地址。
若未使用到C++的多态性,直接用基类对象调用函数时,调用的是未被重写的虚函数表,从中找到要调用的函数的地址;
若使用到C++的多态性,将基类对象指针指向子类对象的地址,此时调用函数时,是从被重写后的虚函数表中进行查找,从而
调用到相应的函数。
注意:
基类指针只能访问到派生类中继承基类的数据,所以出现重写,重写积累的虚函数表,使得指向派生类的基类的对象指针
能够访问到派生类中的新增成员。
单继承情况下,只有一个虚函数表,子类符合重写条件的函数在基类的虚函数表中进行重写,构成新的虚函数表。
多继承
#include<iostream>
using namespace std;
#include<windows.h>
class Base1{
public:
virtual void fun1()
{
cout << "Base1::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base1::fun2()" << endl;
}
};
class Base2{
public:
virtual void fun3()
{
cout << "Base2::fun3()" << endl;
}
virtual void fun4()
{
cout << "Base2::fun4()" << endl;
}
};
class Derived :public Base1, public Base2{
public:
void fun1()
{
cout << "Derived::fun1()" << endl;
}
void fun3()
{
cout << "Derived::fun3()" << endl;
}
virtual void fun5()
{
cout << "Derived::fun5()" << endl;
}
};
int main()
{
Derived d;
Base1 *b1 = &d;
Base2 *b2 = &d;
b1->fun1();
b2->fun3();
system("pause");
return 0;
}
运行结果:
结构示意图:
分析运行结果:
在多继承中,子类Derived继承了基类Base1和基类Base2。在使用多态之前,基类Base1、Base2都有其各自的虚函数表,
表内分别存放着各自虚函数的地址。在派生类多继承基类之后,派生类中存在与基类虚函数构成重写的虚函数,于是基类的虚
函数表发生了变化,用派生类中符合重写条件的虚函数的地址覆盖掉对应的基类虚函数的地址。
值的注意的是,派生类中新增的虚函数的地址存放在Base1的虚函数表中。我们可以记住这个结论,即派生类的新增的虚
函数地址存放在它第一个继承的基类的虚函数表中。
779

被折叠的 条评论
为什么被折叠?



