多态的概念:
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。
那么在继承中要构成多态还有两个条件:
- 必须通过
基类的指针
或者引
用调用虚函数- 被调用的函数必须是虚函数,且派生类必须对基类的
虚函数进行重写
虚函数:即被virtual修饰的类成员函数称为虚函数。
只有成员函数才可以加virtual
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数
三同
:返回值类型、函数名字、参数列表完全相同
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:
// 虚函数重写/覆盖条件 : 虚函数 + 三同(函数名、参数、返回值)
// 不符合重写,就是隐藏关系
// 特例1:子类虚函数不加virtual,依旧构成重写 (实际最好加上)
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};class Soldier : public Person{
public:
virtual void BuyTicket() { cout << "优先买票" << endl; }
};// 多态两个条件:
// 1、虚函数重写// 2、父类指针或者引用去调用虚函数void Func(Person& p)
{
p.BuyTicket();
}int main()
{
//定义
Person ps;
Student st;
Soldier sd;//多态——调用了三个不同的函数Func(ps);
Func(st);
Func(sd);return 0;
}
结果可看出,不同身份的人去买票结果不同,这就构成了多态
注意
:不是父类的指针或者引用调用就不会构成多态
// 1、不是父类的指针或者引用调用class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:
// 虚函数重写/覆盖条件 : 虚函数 + 三同(函数名、参数、返回值)
// 不符合重写,就是隐藏关系
// 特例1:子类虚函数不加virtual,依旧构成重写 (实际最好加上)
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};class Soldier : public Person{
public:
virtual void BuyTicket() { cout << "优先买票" << endl; }
};// 多态两个条件:
// 1、虚函数重写
// 2、父类指针或者引用去调用虚函数//这里不是父类的指针或引用void Func(Person p)
{
p.BuyTicket();
}int main()
{
Person ps;
Student st;
Soldier sd;//不会构成多态Func(ps);
Func(st);
Func(sd);return 0;
}
// 2、不符合重写 -- virtual函数
// ps:特例1:子类虚函数不加virtual,依旧构成重写 (实际最好加上)
class Person
{
public:
void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:
// 虚函数重写/覆盖条件 : 虚函数 + 三同(函数名、参数、返回值)
// 不符合重写,就是隐藏关系
// 特例1:子类虚函数不加virtual,依旧构成重写 (实际最好加上)
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};class Soldier : public Person{
public:
virtual void BuyTicket() { cout << "优先买票" << endl; }
};// 多态两个条件:
// 1、虚函数重写
// 2、父类指针或者引用去调用虚函数void Func(Person& p)
{
p.BuyTicket();
}int main()
{
Person ps;
Student st;
Soldier sd;Func(ps);
Func(st);
Func(sd);return 0;
}
注意:只要父类没有构成虚函数就不能形成多态。
子类是可以不写virtual的,但是父类必须写
// 2、不符合重写 -- 参数不同
class Person
{
public:
//virtual void BuyTicket(char) { cout << "买票-全价" << endl; }
virtual void BuyTicket(int) { cout << "买票-全价" << endl; }
};class Student : public Person {
public:
// 虚函数重写/覆盖条件 : 虚函数 + 三同(函数名、参数、返回值)
// 不符合重写,就是隐藏关系
// 特例1:子类虚函数不加virtual,依旧构成重写 (实际最好加上)
virtual void BuyTicket(int) { cout << "买票-半价" << endl; }
};class Soldier : public Person{
public:
virtual void BuyTicket() { cout << "优先买票" << endl; }
};// 多态两个条件:
// 1、虚函数重写
// 2、父类指针或者引用去调用虚函数void Func(Person& p)
{
p.BuyTicket(1);
}int main()
{
Person ps;
Student st;
Soldier sd;Func(ps);
Func(st);
//Func(sd);return 0;
}
返回值可以不同,但要求必须是父子关系的的指针或者引用
父是父类指针,子是子类指针,不然也不是协变
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;}
};
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
// 建议在继承中析构函数定义成虚函数
class Person {
public:
virtual ~Person() { cout << "~Person()" << endl; }//int* _ptr;
};class Student : public Person {
public:
// 析构函数名会被处理成destructor,所以这里析构函数完成虚函数重写
virtual ~Student() { cout << "~Student()" << endl; }
};int main()
{
Person* ptr1 = new Person;
delete ptr1;Person* ptr2 = new Student;
delete ptr2;//普通场景下没有问题//Person p;
//Student s;return 0;
}
父类不加virtual 子类也不加
new的是子类对象,调用的确实父类的析构函数
不构成多态,这里是普通调用——编译时决议
ptr2->destructor();
operator delete(ptr2);
父类加virtual 子类也加
第一次调用Person析构的是person
第二次调用Person析构的是student里的person
不存在重复析构
注意:父类必须加,子类可加可不加
子类析构函数重写父类析构函数,这里才能正确调用
指向父类调用父类析构函数
指向子类调用子类析构函数
关键字
(另一个作用:修饰一个类,使这个类不能被继承)
了解一下,很少会出现这种场景。因为多态就需要虚函数重写。
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
是加在子类的虚函数上!
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
函数之间的关系
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。间接强制
class Car
{
public:
virtual void Drive() = 0;
};class BMW : public Car
{
public:};int main()
{
//不能实例化出对象
Car c;
BMW b; return 0;
}
class Car
{
public:
virtual void Drive() = 0;
};class BMW : public Car
{
public:
//虚函数重写才可以实例化出对象
virtual void Drive()
{
cout << "操控-好开" << endl;
}
};class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-豪华舒适" << endl;
}
};int main()
{
//不能实例化出对象
//Car c;
//BMW b;//重写之后可以实例化了Car* ptr = new BMW;
ptr->Drive();ptr = new Benz;
ptr->Drive();return 0;
}
只要子类不重写就实例化不出对象的——直接报错
可以说override是检查重写
纯虚函数就是间接强制去重写
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
子类为什么不写virtual都可以?就是因为子类用的是父类的接口,接口继承。用的是自己的实现
所以如果不实现多态,不要把函数定义成虚函数。
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}virtual void Func2()
{
cout << "Func1()" << endl;
}void Func3()
{
cout << "Func1()" << endl;
}private:
int _b = 1;
//char _ch = 'A';
};int main()
{
cout << sizeof(Base) << endl;//12
Base b;return 0;
}
多了一个
__vfptr
虚表指针
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
虚表只是存地址的
虚表被重写
:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void Func() { cout << "Func" << endl; }int _a = 0;
};class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }int _b = 0;
};void Func(Person& p)//引用
{
p.BuyTicket();
}int main()
{
Person Mike;
Func(Mike);Student Johnson;
Func(Johnson);return 0;
}
虚表的本质是一个函数指针的数组
两个的虚表不是同一个,BuyTicket被重写了——达到指向谁调用谁
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
总结
:
派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚
表指针也就是存在部分的另一部分是自己的成员。
基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表
中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数
的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函
数,所以不会放进虚表。
虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
总结一下派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生
类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己
新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
虚函数存在哪的?虚表存在哪的?
答:虚函数存在
虚表,虚表存在对象中。注意上面的回答的错的。
注意
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是
他的指针又存到了虚表中。
另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的
符合多态的两个条件,那么调用时会到指向对象的虚表中找到对应的虚函数地址,进行调用.
(指向父类调父类,指向子类调子类)
- 没有父类的指针或引用去调用
#include
using namespace std;class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }protected:
int _age = 1;
};class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
protected:
int _id;
};// 多态的条件:
// 1、虚函数的重写
// 2、父类指针或者引用去调用void Func1(Person& p)
{
p.BuyTicket();
}void Func2(Person p)
{
p.BuyTicket();
}int main()
{
Person ps;
Student st;Func1(ps);
Func1(st);Func2(ps);
Func2(st);return 0;
}
结果:有父类的引用的是多态,没有引用的就不是多态。
构成多态的调用
,运行时到指向对象的虚表中找调用虚函数地址,所以p指向谁就调用谁的虚函数(运行时决议
)不构成多态
,普通调用,编译时确定调用函数的地址(编译时决议
)
- 虚函数不构成重写
不构成重写——从运行时决议变成编译时决议
父类构成重写,子类没有构成——还是运行时决议
原因:父类的虚表和子类的虚表都是父类的虚函数
class Person {
public:
//void BuyTicket() { cout << "买票-全价" << endl; }
virtual void BuyTicket() { cout << "买票-全价" << endl; }protected:
int _age = 1;
};class Student : public Person {
public:
//virtual void BuyTicket() { cout << "买票-半价" << endl; }
protected:
int _id;
};// 多态的条件:
// 1、虚函数的重写
// 2、父类指针或者引用去调用// 虚函数重写:
// 1、虚函数
// 2、函数名、参数、返回值void Func1(Person& p)
{
p.BuyTicket();
}void Func2(Person p)
{
p.BuyTicket();
}int main()
{
Person ps;
Student st;Func1(ps);
Func1(st);Func2(ps);
Func2(st);return 0;
}
不构成虚函数重写的,无论父类是否是引用或指针都不构成多态!!
总结:不构成虚函数没有价值
如何用代码访问虚表
:
单继承的情况下都只有一个虚表。
虚表是放在基类的。
Class Person
{public:virtual void BuyTicket(){cout << "买票——全价"<< endl;}};int main()
{Person p1;Person p2;return 0;
}
这里p1和p2是否公用一个虚表?还是各自用各自的?公用一个虚表。
说明:同一个类型的对象公用一个虚表
Class Person
{public:virtual void BuyTicket(){cout << "买票——全价"<< endl;}};class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};int main()
{Person p1;Person p2;Student s1;Student s2;return 0;
}
这里的p1与s1就不是公用一个虚表。
不同的对象用的不是同一个虚表
如果没有完成重写的情况呢?
虽然里面的内容都是一样的,但是不是同一个虚表。
注意:在vs下,不管是否完成重写,子类的虚表和父类的虚表都不是同一个
Class Person
{public:virtual void BuyTicket(){cout << "买票——全价"<< endl;}vietual viod Func1(){}
};class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
vietual viod Func2(){}
};int main()
{Person p1;Person p2;Student s1;Student s2;return 0;
}
如果子类有一个没有重写的虚函数,也会单独开一个虚表,但是在vs下的监视窗口中,子类的虚函数func2看不见,父类中func1是看得见的。
难道子类中的虚函数没有进虚表吗?进!
想看子类中到底func2有没有进虚表:
1.通过内存窗口可以基本确认有一个func2,但不能百分百确定就是func2
2.写一个函数打印虚函数表:
定义函数指针:
void(*ptr)();
//在函数里面打印一句话,方便知道调用了谁
class Person
{
public:
virtual void BuyTicket()
{
cout << "Person::买票-全价" << endl;
}virtual void Func1()
{
cout << "Person::Func1()" << endl;
}
};class Student : public Person {
public:
virtual void BuyTicket()
{
cout << "Student::买票-半价" << endl;
}virtual void Func2()
{
cout << "Student::Func2()" << endl;
}
};//虚表是一个函数指针的数组typedef void(*VFPTR)();//注意类型要放中间,与定义一致//void PrintVFTable(VFPTR table[])
//void PrintVFTable(VFPTR* table, size_t n)void PrintVFTable(VFPTR* table)
{
//vs下,虚表最后都给一个空指针,所以不用写死
for (size_t i = 0; table[i] != nullptr; ++i)
//for (size_t i = 0; i < n; ++i)
{
printf("vft[%d]:%p->", i, table[i]);
//两个方法都一样//table[i]();//虚函数的地址是可以直接调用的
VFPTR pf = table[i];
pf();//这样是私有的都可以访问,特殊的
}
cout << endl;
}int main()
{
// 同一个类型的对象共用一个虚表
Person p1;
Person p2;// vs下 不管是否完成重写,子类虚表跟父类虚表都不是同一个
Student s1;
Student s2;//虚表的地址在对象的头4个(32位)或头8个(64位)字节//不能直接强转,必须借助指针——间接方法
//4——int*
//8——long long*
//强转两次PrintVFTable((VFPTR*)*(int*)&s1);
PrintVFTable((VFPTR*)*(int*)&p1);return 0;
}
两种强转,完全不一样
*(int*)&s1;//int 与指针可以转,两者是有一点关联
(int)s1;//两种完全不同的类型,有时候编译器是不支持的
打印父类时会多一个空指针,vs的一个bug,可以清理一下再生成
或者改变传参个数就行
typedef void(*VFPTR)();
void PrintVFTable(VFPTR* table, size_t n){for (size_t i = 0; i < n; ++i)
{
printf("vft[%d]:%p->", i, table[i]);VFPTR pf = table[i];
pf();
}
cout << endl;
}int main()
{
// 同一个类型的对象共用一个虚表
Person p1;
Person p2;// vs下 不管是否完成重写,子类虚表跟父类虚表都不是同一个
Student s1;
Student s2;PrintVFTable((VFPTR*)*(int*)&s1,3);
PrintVFTable((VFPTR*)*(int*)&p1,2);return 0;
}
class Person
{
public:
virtual void BuyTicket()
{
cout << "Person::买票-全价" << endl;
}virtual void Func1()
{
cout << "Person::Func1()" << endl;
}
};class Student : public Person {
public:
virtual void BuyTicket()
{
cout << "Student::买票-半价" << endl;
}virtual void Func2()
{
cout << "Student::Func2()" << endl;
}
};typedef void(*VFPTR)();//void PrintVFTable(VFPTR table[])
//void PrintVFTable(VFPTR* table, size_t n)
//打印虚函数表中的虚函数地址,并调用虚函数
void PrintVFTable(VFPTR* table)
{
for (size_t i = 0; table[i] != nullptr; ++i)
//for (size_t i = 0; i < n; ++i)
{
printf("vft[%d]:%p->", i, table[i]);
//table[i]();
VFPTR pf = table[i];
pf();
}
cout << endl;
}class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1 = 1;
};class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2 = 2;
};class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d = 3;
};int main()
{
// 同一个类型的对象共用一个虚表
Person p1;
Person p2;// vs下 不管是否完成重写,子类虚表跟父类虚表都不是同一个
Student s1;
Student s2;/*PrintVFTable((VFPTR*)*(int*)&s1, 3);
PrintVFTable((VFPTR*)*(int*)&p1, 2);*/Derive d;
//cout << sizeof(d) << endl;//20//取对象头部虚函数表指针传递过去//PrintVFTable((VFPTR*)(*(int*)&d));
PrintVFTable((VFPTR*)(*(int*)((char*)&d+sizeof(Base1))));//Base2* ptr2 = &d;
//PrintVFTable((VFPTR*)(*(int*)(ptr2)));// 符合多态,去指向对象虚函数表中去找func1的地址调用
Base1* ptr1 = &d;
ptr1->func1();Base2* ptr2 = &d;
ptr2->func1();//d.func1();return 0;
}
//第一个虚表——头4个
PrintVFTable((VFPTR*)(*(int*)&d));
//第二个虚表——中间 1.
//PrintVFTable((VFPTR*)(*(int*)((char*)&d+sizeof(Base1))));
//2. 切片
Base2* ptr2 = &d;
PrintVFTable((VFPTR*)(*(int*)(ptr2)));
重写之后会被覆盖,但为什么func1的地址不一样?
切片指针偏移问题,逆向研究
// 符合多态,去指向对象虚函数表中去找func1的地址调用
Base1* ptr1 = &d;
ptr1->func1();//普通调用Base2* ptr2 = &d;
ptr2->func1();
#include
#include
using namespace std;class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1 = 1;
};class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2 = 2;
};class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1 = 3;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :%p,->", 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);printf("%p\n", &Derive::func1);//强制调用
auto ptr = &Derive::func1;
cout << typeid(ptr).name() << endl;//编译时决议
d.func1();//直接调用func1,没有进虚表,与上上面打印出来的地址也不一样//形成多态调用——运行时决议
Base1* ptr1 = &d;
ptr1->func1();Base2* ptr2 = &d;
ptr2->func1();return 0;
}
前面走的不一样,但内核都是调用了func1