《C++ 多态》
目录
实现多态的重要条件
静态绑定和动态绑定
重载,重写,隐藏
变态题
析构函数的重写
虚函数表
实现多态的重要条件
基类对象的指针或引用在调用同一函数时产生了不同的效果,这就是多态。
1.必须是基类的指针或引用调用虚函数。
2.虚函数必须在派生类中完成了重写。
静态绑定和动态绑定
1.对于不满足多态的函数调用,程序在编译时就确定了函数调用的地址,完成了函数参数的绑定,就是静态绑定。
2.对于满足多态的函数调用,程序会在运行时去虚函数表寻找函数的地址调用,就是动态绑定。
重载,重写,隐藏
在相同作用域中,函数名相同,参数个数或类型不同的函数,构成重载。
在基类域和派生类域中函数名相同,参数相同,返回值相同的函数,构成重写。
在基类域和派生类域中函数名相同,成员变量名相同,派生类的函数/变量对基类的构成隐藏。
变态题
答案是"B->1"。
解析:
派生类B对象的指针p指向堆上开辟的B的对象。
p调用基类A中的虚函数test,在A::test()内部调用func()时,检查func()的声明发现是虚函数,但是默认参数val=1在编译时直接嵌入调用处,完成静态绑定,代码等价于this->func(1)。
但是p是派生类B对象的指针,也就是说这个this是B*的切片A*指向的是派生类对象中基类的那一部分,这里B中重写了func函数,所以调用的是B重写的函数部分。
//下面程序的运行结果是什么
class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};class B : public A
{public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}
析构函数的重写
下面这段程序运行后不会调用B中的析构函数,造成内存泄露。
原因是编译器会在编译时将析构函数名称都改为destructor此时基类A和派生类B的析构函数是隐藏关系。在delete p2时调用析构函数,p2是基类的指针,本来这里的目的是实现多态让p2调用B的析构函数,但是这里析构函数不是虚函数所以无法通过虚函数表找到B的析构函数的地址,所以只会根据静态绑定调用A的析构函数,造成内存泄漏。
正确做法是将基类的析构函数置为虚函数,这样p2调用析构函数(同时也因为p2指向的是派生类对象的基类那一部分)就会通过虚函数表找到B的析构函数去调用,再自动调用A的析构函数。
class A
{
public://virtual ~A()~A(){cout << "~A()" << endl;}
};
class B : public A {
public:~B(){cout << "~B()->delete:" << _p << endl;delete _p;}
protected:int* _p = new int[10];
};int main()
{A* p1 = new A;A* p2 = new B;//delete的原理//p1->~destructor operator deletedelete p1;delete p2;return 0;
}
虚函数表
下面的程序输出是8,这是因为除了成员变量a外,打开监视窗口会发现还有一个成员_vfptr,这是一个虚函数表。虚函数表本质是一个指针数组,这个指针数组中存放的是虚函数的指针。
基类的虚函数表会存放基类的虚函数指针;派生类的虚函数表存放基类的虚函数指针,派生类重写的虚函数指针会覆盖基类的虚函数指针,派生类虚函数指针。派生类的基类和原始的基类是不同的。
class Base{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : public Base
{
public:// 重写基类的func1virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};
int main()
{Base b;cout << sizeof(b) << endl;Derive d;return 0;
}
"Keep coding, stay curious! 🚀"