虚函数
问题:还记得第7章的例子吗?
例7-3 类型转换规则举例
//例7-3类型转换规则举例
#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
void display() const {
cout << "Base1::display()" << endl;
}
};
class Base2: public Base1 { //公有派生类Base2定义
public:
void display() const {
cout << "Base2::display()" << endl;
}
};
class Derived: public Base2 { //公有派生类Derived定义
public:
void display() const {
cout << "Derived::display()" << endl;
}
};
void fun(Base1 *ptr) { //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
}
int main() { //主函数
Base1 base1; //声明Base1类对象
Base2 base2; //声明Base2类对象
Derived derived; //声明Derived类对象
fun(&base1); //用Base1对象的指针调用fun函数
fun(&base2); //用Base2对象的指针调用fun函数 调用的还是Base1里面的display函数
fun(&derived); //用Derived对象的指针调用fun函数 调用的还是Base1里面的display函数
return 0;
}
结论:不要重新定义继承而来的非虚函数
解决办法:第八章虚函数讲解
例8-4 通过虚函数实现运行时多态
现在来改进一下上述程序
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void display() const; //虚函数
};
void Base1::display() const {
cout << "Base1::display()" << endl;
}
class Base2: public Base1 {
public:
virtual void display() const;
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 {
public:
virtual void display() const;
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) {
ptr->display();
}
int main() {
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
return 0;
}
初识虚函数(属于对象而不属于类)
-
用virtual关键字说明的函数
-
虚函数是实现运行时多态性基础
-
C++中的虚函数是动态绑定的函数
-
虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
-
一般成员函数可以是虚函数
-
构造函数不能是虚函数
-
析构函数可以是虚函数
什么函数可以是虚函数?
- 一般成员函数可以是虚函数
- 构造函数不能是虚函数
- 析构函数可以是虚函数
一般虚函数成员
- 虚函数的声明:virtual 函数类型 函数名(形参表);
- 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候
- 在派生类中可以对基类中的成员函数进行覆盖
- (千万不要重写继承而来的非虚函数)
- 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。
virtual关键字
派生类可以不显式的用virtual声明虚函数,这时系统就会用以下规则来判断派生类的一个函数成员是不是虚函数:
- 该函数是否与基类的虚函数有相同的名称、参数个数及队形参数类型;
- 该函数是够与积累的虚函数有相同的返回值或者满足类型箭筒规则的指针、引用型的返回值。
如果从名称、参数及返回值三个方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。
派生类的虚函数还会隐藏基类中同名函数的所有其他重载形式。(需要用类名)
一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。
虚析构函数
为什么需要虚析构函数?
- 可能通过基类指针删除派生类对象;
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
一个不使用虚析构函数的例子
#include <iostream>
using namespace std;
class Base {
public:
~Base(); //不是虚函数
};
Base::~Base() {
cout<< "Base destructor" << endl;
}
class Derived: public Base{
public:
Derived();
~Derived(); //不是虚函数
private:
int *p;
};
Derived::Derived() {
p = new int(0);
}
Derived::~Derived() {
cout << " Derived destructor" << endl;
delete p;
}
void fun(Base *b){
delete b;
}
int main(){
Base *b = new Derived();
fun(b);
return 0;
}
结果为
Base destructor
改进后的虚析构函数
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base(); //不是虚函数
};
Base::~Base() {
cout<< "Base destructor" << endl;
}
class Derived: public Base{
public:
Derived();
virtual ~Derived(); //不是虚函数
private:
int *p;
};
Derived::Derived() {
p = new int(0);
}
Derived::~Derived() {
cout << "Derived destructor" << endl;
delete p;
}
void fun(Base *b){
delete b;
}
int main(){
Base *b = new Derived();
fun(b);
return 0;
}
运行结果为
Derived destructor
Base destructor
虚表与动态绑定
-
虚表
-
每个多态类有一个虚表(virtual table)
-
虚表中有当前类的各个虚函数的入口地址
-
每个对象有一个指向当前类的虚表的指针(虚指针vptr)
-
-
动态绑定的实现
-
构造函数中为对象的虚指针赋值
-
通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
-
通过该入口地址调用虚函数
-