C++的三大件的最后一件——多态
目录
一多态
1概念
通俗来说:多种形态;
具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
比如买票这个行为:
当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。
2构成条件
1. 必须通过基类的指针或者引用调用虚函数(被virtual修饰的函数叫做虚函数)
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
那么:虚函数重写又是什么呢?
3重写(覆盖)
子类对象对父类的虚函数进行重写必须要满足三点(三同):函数名相同,参数相同,返回值相同
以购票为例:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
但重写有两个例外:
1. 协变(基类与派生类虚函数返回值类型可以不同)
class A {};
class B : public A {};
class Person {
public:
virtual A* f() { return new A; }//返回基类的类型
};
class Student : public Person {
public:
virtual B* f() { return new B; }//返回子类的类型
};
2派生类重写虚函数可以不加virtual
可以理解为派生类继承了基类的virtual;但还是建议加上!
3.析构函数的重写(基类与派生类析构函数的名字不同)
在最后处理的时候,编译器把父类与子类的析构函数统一处理成destructor函数名构成重写
class Person {
public:
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
return 0;
}
认识了重写,现在让我们来做一道选择题:
以下程序输出结果是什么()
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()
{
B* p = new B;
p->test();
return 0;
}
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
正常理解:A,B对象的fuc构成重写,p->test()去调用func()时会去调用B对象的fuc,所以选D
但是:答案选的是B:这就很怪^?^
原因:虚函数重写的是函数体的实现,其它部分是复用父类的(val用的是父类的缺省值)
4C++的override和final
由于构成重写的条件相对严格,而够不够成重写编译器都不会报错,C++会我们提供了final和override来检查是否构成重写;
final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() { cout << "Benz-舒适" << endl; }//报错
};
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Car
{
public:
virtual void Drive() {}
};
class Benz :public Car
{
public:
virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
5三重的对比
重载、重写(覆盖)、重定义(隐藏)的对比:
二多态的原理
1虚函数表
// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
可能你会说:不就只有_b变量吗?我们通过监视窗口来观察:
除了_b变量,还存在着这个指针,那这个指针是什么?指向的内容又是什么?
对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function),简称虚表指针
要了解指向的内容,我们通过下面的代码来演示:(满足多态的情况)
class Person {
public:
void BuyTicket() { cout << "买票-全价" << endl; }
private:
int _i = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _j = 2;
};
void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person Mike;
Func(&Mike);
Person p1;
Func(&p1);
Student Johnson;
Func(&Johnson);
return 0;
}
1.p指针指向的内容可以说都是父类的对象模型(子类也是,只不过子类重写了父类的那部分)
2.同类型的虚表是共享的;而不同类型的虚表是不同的
3.当满足多态的条件时,就会去指向对象的虚表中找到对应的虚函数进行调用
(这是在运行时就已经确定的)
而不满足多态时:在编译链接时就根据对象来类型,确定调用函数
(上面p引用改为p指针就不满足多态了)
2单继承中的虚函数表
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a = 1;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
在上面的代码中,d对象会拷贝Base类型中的虚表,重写func1()的虚函数(覆盖);
然后将自己的虚函数依次填到虚表中;但通过监视窗口我们可以看到:
fuc3()和fuc4()没有写进虚表中,但打开内存窗口观察却(疑似)有?
(要明白:虚函数表本质上是函数指针数组)
我们就要想办法来打印出虚函数表:
typedef void(*VFPTR)();//函数指针数组要先进行typedef
void PrintVFPTR(VFPTR* ptr)
{
for (int i = 0; i < 4; i++)
{
printf("%p->", ptr[i]);//打印地址
VFPTR Ptr = ptr[i];
(*Ptr)();//根据地址进行调用
}
}
int main()
{
Base b;
Derive d;
//打印b中虚函数表
//int ptr = (int)d;//不支持强制
VFPTR* ptr = (VFPTR*)(*( (int*)(&d) ));//取地址进行强转
PrintVFPTR(ptr);
return 0;
}
不仅把地址给打印出来,还把地址所在的位置的虚函数给打印出来了,更好的进行证明!!
3虚表的位置
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
int main()
{
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxxxx";
Person p;
Student s;
Person* p3 = &p;
Student* p4 = &s;
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
printf("Person虚表地址:%p\n", *(int*)p3);
printf("Student虚表地址:%p\n", *(int*)p4);
return 0;
}
从打印结果可以看出:
虚表存在常量区中;要注意:对象里面存的是虚表的地址,而不是虚表!!
4多继承中的虚函数表(了解)
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
通过d对象的模型来进行分析:
如果继承的父类中有虚表:子类中的虚函数就放到先继承的父类的虚表,自己就不在创建虚表!!
而多继承中的菱形继承也是类似的:
//菱形虚拟继承
class A
{
public:
virtual void func1() { cout << "A::func1" << endl; }
int _a;
};
//class B : public A
class B : virtual public A
{
public:
virtual void func2() { cout << "B::func2" << endl; }
int _b;
};
//class C : public A
class C : virtual public A
{
public:
virtual void func3() { cout << "C::func3" << endl; }
int _c;
};
class D : public B, public C
{
public:
virtual void func4() { cout << "D::func4" << endl; }
int _d;
};
int main()
{
D d;
//cout << sizeof(d) << endl;
//菱形继承的对象模型跟多继承类似
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
通过内存窗口来分析:
C++中有了多继承,就有了菱形虚拟继承,实现与理解成本非常高,一般在实践中很少用到
以上便是在学习多态中的一些相关内容:有问题欢迎在评论区指出,感谢!!