动态绑定
- 对象的静态类型:对象在声明时采用的类型,在编译期确定,静态类型无法更改
- 对象的动态类型:所指对象的类型,在运行期决定。对象的动态类型可以更改,
- 静态绑定:绑定的是对象的静态类型,发生在编译期
- 动态绑定:绑定的是对象的动态类型
class B{
public:
void doSomething() {
}
virtual void vfun() {
}
};
class C : public B{
public:
void doSomething() {//子类中是一个非虚函数,这样会造成名称遮挡
}
virtual void vfun() {
}
};
class D : public B {
public:
void doSomething() { }//子类中是一个非虚函数,这样会造成名称遮挡
virtual void vfun() { }
}
D* pD = new D();
B* pB = pD;
// pD和pB都指向同一个对象,但是doSomething()是no-virtual函数,是静态绑定的
// 编译期会根据对象的静态类型来选择函数
pD->doSomething(); //调用的是D::doSomething()
pB->doSomething(); //调用的是B::doSomething()
//由于vfun是虚函数,它是动态绑定的,虽然pD,PB的静态类型不同,但是指向同一个对象
// 动态类型是相同的,都是D*,所以它俩调用的是同一个B::vfun()。
pD->vfun();
pB->vfun();
哪些是动态绑定哪些是静态绑定:
只有当使用指针或者引用时调用虚函数才使用动态绑定,其他的全是静态绑定。
缺省参数是静态绑定的。
具有继承关系的类之间发生类型转换:
- 从派生类向基类的类型转换只对指针或引用有效
- 基类向派生类不存在隐式类型转换
- 派生类向基类的类型转换可能会由于访问受限存在问题
#include <iostream>
using namespace std;
//class A final { } final 关键字可以防止继承的发生
class A {
public:
A() =default;
virtual void f() const {
cout << "A::f()" << endl;
}
virtual ~A() {}
};
class B : public A {
public:
B() =default;
void f() const override{ // C++11 中使用override来说明派生类中的虚函数,方便清晰定位
cout << "B::f()" << endl;
}
~B(){}
};
void call(A &i){ //这里使用基类作为形参的引用,可以处理派生类和基类,实现代码复用,OOP
i.f();
}
int main()
{
B b;// B的内存分布中,虚函数表位于函数指针位置起始位置,B中的f()覆盖了基类的f()指针,
A a;
A *pB = new B;
call(b); // 调用b的f()
call(a); //调用 a的f();
delete pB;
// 先如果基类的析构函数是虚函数,则先调用基类的析构函数,在调用派生类的析构函数,如果基类的析构函数不是虚函数,则只析构基类造成泄露。
// 但是对于局部变量离开作用域,则是先调用派生的析构函数,然后在调用基类的析构函数
// b是局部变量,析构的时候不是动态绑定,因此会依次调用~B,~A。
// 但pB是A类型的,在使用delete时如果A的析构函数不是虚函数,则会只调用A的析构函数,造成子类的内存泄露
// 而如果A的析构函数是虚函数,由于存在动态绑定,并且通过指针或者引用,存在派生类向基类转换则,delete会在调用完~A,发现~A是虚函数,再调用~B,完成全部的析构。
return 0;
}