1.多态的概念
同一件事物,在不同场景下表现出的不同的状态
举例:买票行为,学生半价,普通人全价,军人优先
分类:静态多态和动态多态
静态多态:编译器在编译时确定具体调用哪个函数。体现:函数重载,模板动态多态:编译时无违法确定调用哪个函数,只有在代码运行时才知道具体应该调用哪个函数 体现:虚函数+重写
- 动态多态的实现
2.1C++中实现多态的条件>>在继承体系中,基类中必须有虚函数,派生类必须对基类中的虚函数进行重写>>必须通过基类的指针或引用去调用虚函数体现:基类的 指针或引用指向哪个类的对象,程序运行时就会调用哪个类的虚函数
#include<iostream>
#include<string>
using namespace std;
class person {
public:
virtual void BuyTicket() { cout << "全价购票" << endl;
}
protected:
string _name;
string _dender;
int _age;
};
class student:public person {
public:
virtual void BuyTicket() { cout << "半价购票" << endl;
}
};
void Test(person&p){
p.BuyTicket();
}
int main() {
person p;
Test(p);
student s;
Test(s);
return 0;
}
2.2 重写
派生类重写基类的虚函数,必须保证派生类虚函数的原型(函数名,返回值类型,参数列表)与基类的虚函数完全一致,虚函数的重写也叫做虚函数的覆盖
例外:协变–基类的虚函数返回基类的指针或引用,派生类的虚函数返回派生类的指针或引用(返回值类型不同)
#include<iostream>
using namespace std;
class A
{};
class B:public A
{};
A a;
B b;
class person {
public:
virtual A& BuyTicket() {
cout << "全价购票" << endl;
return a;
}
};
class student :public person {
public:
virtual B& BuyTicket() {
cout << "半价购票" << endl;
return b;
}
};
void Test(person& p) {
p.BuyTicket();
}
int main() {
person p;
student s;
Test(p);
Test(s);
return 0;
}
析构函数的重写–只要基类的析构函数给成虚函数,派生类的析构函数一旦显式提供,就可以构成重写(函数名不同)
#include<iostream>
using namespace std;
class person {
public:
virtual ~person() {
cout << "person::~person" << endl;
}
};
class student :public person {
public:
virtual ~student() {
cout << "student::~student" << endl;
}
};
int main() {
person* p1 = new person;
delete p1;
p1 = new student;
delete p1;
return 0;
}
3.c++11关键字override和final
override修饰派生类虚函数强制完成重写,如果没有重写会编译报错
#include<iostream>
using namespace std;
class person {
public:
virtual void TestFunc() {
cout << "person::TestFunc()" << endl;
}
};
class student:public person{
virtual void TestFunc()override {
cout << "student::TestFunc()" << endl;
}
};
void Test(person&p) {
p.TestFunc();
}
int main() {
person p;
student s;
Test(p);
Test(s);
return 0;
}
final修饰基类的虚函数不能被派生类的虚函数重写
#include<iostream>
using namespace std;
class person {
public:
virtual void TestFunc()final {
cout << "person::TestFunc()" << endl;
}
};
class student :public person {
virtual void TestFunc() {
cout << "student::TestFunc()" << endl;
}
};
void Test(person& p) {
p.TestFunc();
}
int main() {
person p;
student s;
Test(p);
Test(s);
return 0;
}
4.抽象类
将包含纯虚函数的类,抽象类不能实例化对象
#include<iostream>
using namespace std;
const double PI = 3.14159;
class shape {
public:
virtual void disp() = 0;
void setvalue(int x, int y=0) {
_x = x;
_y = y;
}
protected:
int _x;
int _y;
};
class Square :public shape {
public:
virtual void disp() {
cout << "矩形面积:"<<_x * _y << endl;
}
};
class Circle :public shape {
public:
virtual void disp() {
cout << "圆面积" << PI * _x * _x << endl;
}
};
void Test() {
shape* s = nullptr;
Square sq;
Circle c;
s = &sq;
s->setvalue(2, 4);
s->disp();
s = &c;
s->setvalue(10);
s->disp();
}
int main() {
Test();
return 0;
}
5:多态的实现
如果一个类有虚函数,编译器会给该类的 对象增加4个字节
函数虚表的构建规则:
将基类虚函数按照其在类中的声明次序依次增加到虚表中
同一个类的不同对象在底层共用该类的虚表
虚表的存放位置:虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),微软的编译器将虚函数表存放在常量段中
只要基类中存在虚函数,即使派生类未对基类中的虚函数进行重写,派生类也有属于自己的虚表(与基类没有共用),但是派生类虚表中内容与基类虚表中内容完全相同。>>将基类虚表中内容拷贝一份放入派生类虚表中>>如果派生类重写了基类的虚函数,编译器将会使用派生类自己的虚函数替换虚表中相同偏移量位置的虚函数>>对于派生类新增加的虚函数按照声明次序放在虚表的后面
#include<iostream>
using namespace std;
class Base {
public:
virtual void TestFunc1() {
cout << "Base::TestFunc1()" << endl;
}
virtual void TestFunc2() {
cout << "Base::TestFunc2()" << endl;
}
virtual void TestFunc3() {
cout << "Base::TestFunc3()" << endl;
}
protected:
int _b;
};
class Derived :public Base {
public:
virtual void TestFunc5() {
cout << "Derived::TestFunc5()" << endl;
}
virtual void TestFunc1() {
cout << "Derived::TestFunc1()" << endl;
}
virtual void TestFunc3() {
cout << "Derived::TestFunc3()" << endl;
}
virtual void TestFunc4() {
cout << "Derived::TestFunc4()" << endl;
}
protected:
int _d;
};
typedef void(*PVF)();
void printVFT(Base& b, string str) {
cout << str << endl;
//1.从对象前4个字节取虚表的地址
PVF* pVF = (PVF*) * (int*)& b;
while (*pVF) {
(*pVF)();
pVF++;
}
cout << endl;
}
int main() {
Base b;
Derived d;
printVFT(d, "Derived VFT");
return 0;
}
多继承的对象模型
class B1 {
public:
virtual void TestFunc1()
{
cout << "B1::TestFunc()" << endl;
}
virtual void TestFuunc2() { cout << "B1::TestFunc2()" << endl;
}
int _b1;
};
class B2 {
public:
virtual void TestFunc3() { cout << "B2::TestFunc3()" << endl;
}
virtual void TestFunc4() { cout << "B2::TestFunc4()" << endl;
}
int _b2;
};
class D :public B1, public B2 {
public:
virtual void TestFunc1() { cout << "D::TestFunc1()" << endl;
}
virtual void TestFunc3() { cout << "D::TestFunc3()" << endl;
}
virtual void TestFunc5() { cout << "D::TestFunc5()" << endl;
}
int _d;
};
typedef void(*PVF)();void printVFT(B1&b,string str) {
cout << str << endl;
PVF* pVF = (PVF*) * (int*)(&b); while (*pVF) {
(*pVF)();
pVF++;
}
cout << endl;
}
void printVFT(B2& b, string str) { cout << str << endl;
PVF* pVF = (PVF*) * (int*)(&b); while (*pVF) {
(*pVF)();
pVF++;
}
cout << endl;
}
int main() {
D d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
B1& b1 = d;
printVFT(b1, "B1 VFT:");
B2& b2 = d;
printVFT(b2, "B2 VFT:");
return 0;}
菱形继承的对象模型
class B {
public:
virtual void TestFunc1() { cout << "B::TestFunc1()" << endl;
}
virtual void TestFunc2() { cout << "B::TestFunc2()" << endl;
}
int _b;
};
class C1:public B {
public:
virtual void TestFunc1() { cout << "C1::TestFunc1()" << endl;
}
virtual void TestFunc3() { cout << "C1::TestFunc3()" << endl;
}
int _c1;
};
class C2: public B {
public:
virtual void TestFunc2() { cout << "C2::TestFunc2()" << endl;
}
virtual void TestFunc4() { cout << "C2::TestFunc4()" << endl;
}
int _c2;
};
class D:public C1,public C2 {
public:
virtual void TestFunc3() { cout << "D::TestFun3()" << endl;
}
virtual void TestFunc4() { cout << "D::TestFunc4()" << endl;
}
virtual void TestFunc5() { cout << "D::TestFunc5()" << endl;
}
int _d;
};
typedef void (*PVF)();
void printVFT(C1& c, string str) { cout << str << endl;
PVF* pVF = (PVF*) * (int*)(&c); while (*pVF) {
(*pVF)();
pVF++;
}
cout << endl;
}
void printVFT(C2& c, string str) { cout << str << endl;
PVF* pVF = (PVF*) * (int*)(&c); while (*pVF) {
(*pVF)();
pVF++;
}
cout << endl;
}
int main() {
D d;
d._c1 = 1;
d._c2 = 2;
d._d = 3;
C1& c1 = d;
printVFT(c1, "VFT C1:");
C2& c2 = d;
printVFT(c2, "VFT C2:");
return 0;
}
6.多态面试题:重载,重写,重定义的区别
重载:函数名相同,函数的参数个数,参数类型或参数顺序三者中必须至少有一个不同,函数的返回值类型可以相同,也可以不相同,发生在开一个类内部
重写:也叫做覆盖,一般发生在子类和父类继承关系之间,子类重新定义父类中有相同名称和参数的虚函数
重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数(参数列表可以不同),指派生类的函数屏蔽了与其同名的基类函数。发生在继承中。
重写时需要注意:
1.被重写的函数不能是static的,必须是virtual的
2.重写函数必须有相同的类型,名称和参数列表
3,重写函数的访问修饰符可以不同,尽管virtual是private的,派生类中重写改写为public,protected也是可以的
静态函数不能作为虚函数
原因:
1> 从技术层面上说:静态函数的调用不需要传递this指针,但是虚函数的调用需要this指针来找到虚函数表
2>从存在的意义上说:静态函数的存在是为了让所有类共享可以在对象产生之前执行一些操作,与虚函数的作用不是一路的
构造函数不能是虚函数
假设构造函数可以作为虚函数:构造函数存储在虚表中调用时必须要通过对象找到虚表,从虚表中找到构造函数但是构造函数没有调用,对象就不完整,最主要:构造函数中编译器才将虚表的地址放到对象的前四个字节中
析构函数可以是虚函数
在继承体系中,如果派生类中涉及到资源的管理,必须将基类的析构函数设置为虚函数,否则就会存在资源泄漏
class A {
public:
virtual ~A() {
cout << "A::~A()" << endl;
}
};
class B :public A {
public:
B() {
cout << "B::B()" << endl;
p = new int[10];
}
~B() {
cout << "B::~B()" << endl; delete[]p;
}
protected:
int* p;
};
int main() {
A* pa = new B;
delete pa;
return 0;
}
本文介绍了C++中的多态概念,包括静态多态和动态多态,重点讲解了动态多态如何通过虚函数实现。内容涵盖虚函数、重写、抽象类、虚表的构建规则以及多继承下的对象模型。还探讨了重载、重写和重定义的区别,并强调了构造函数不能是虚函数而析构函数可以的原因。

774

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



