文章目录
多态感觉真挺难的,这篇就水一水了
引言 多态:多种形态 静态的多态:函数重载,同一个函数看起来调用同一个函数有不同的行为 动态的多态: 静态:是在编译的时候实现的
动态的多态 一个父类的引用或指针去调用同一个函数,如果传递不同的对象,会调用不同的函数 动态:运行时实现的
举个例子: 买票这个行为,对于不同的人价格不同,普通人去买价格低,而有钱人买价格高
扫红包:也是一种多态行为,根据行为去分类,鼓励新用户用,给的红包就多,
虚函数 子类中满足三同函数名,参数,返回值相同 的虚函数,就叫做重写(覆盖),只有虚函数 本质就是不同的人去做同一件事情,结果不同 virtual(只能是类的非静态的成员函数才能是虚函数):其他函数不能称为虚函数 :在最前面加一个virtual
静态成员函数不能加virtual
否则就是隐藏的关系
多态的构成条件(少一个都不行)
必须通过基类 的指针或者引用 调用虚函数
被调用的函数必须是虚函数 ,且派生类必须对基类的虚函数进行重写
重写要求返回值相同,有一个例外:协变:要求返回值是父子关系的指针或者引用 (基本不会见到)
class A class B
1 2 3 4 5 6 7 8 void func (Person p) { p.BuyTicket (); }func (st); func (ps);
析构函数的重写(析构函数) 析构函数也可以是虚函数,构成重写(函数名,返回值,参数,virtual)都相同 把析构函数都被特殊处理成destructor, 完成重写,构成多态,那么才能正确调用析构函数只有动态申请的对象给了父类指针管理才可以,那么就需要定义成虚函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Person {public : virtual ~Person () { cout << "person" << endl; } };class Student : public Person {public : virtual ~Student () { cout << "student" << endl; } };int main () { Person *p1 = new Person; Person *p2 = new Student; Student *p3 = new Student; delete p1; delete p2; delete p3; return 0 ; }
虚函数的重写允许 :两个都是虚函数,或者父类是虚函数,再满足三同就构成重写 虽然子类没有写virtual 是因为他继承了父类的virtual的属性,也完成了重写(是因为析构函数,父类析构函数加上virtual,就不存在不构成多态的了,没钓到子类析构函数,内存泄漏的) :在public:private也是可以的
一般来说父类是别人写的,子类是我们写的,只要父类加了virtual,子类就不用加virtual 我们建议都写上
C++11 override 和final final 设计一个不能被继承的类 c++ 98的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class A {private : A (int a = 0 ) : _a(a) { }public : static A createobj (int a = 0 ) { return A (a); }protected : int _a; };class B :public A { };int main () { A aa=A::createobj (10 ); return 0 ; }
c++ 11
使用final 这个类就无法被继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class A final { protected : int _a; };class B :public A { };int main () { return 0 ; }
使用final这个函数就无法被重写,放在父类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class C { public : virtual void f () final { cout<<"hello " <<endl; } };class D :public C { public : virtual void f () { cout<<"world" <<endl; } };int main () { return 0 ; }
override 同样也是放在类的后面 放在子类重写的虚函数的后面,检查是否完成重写,如果没有完成重写就会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class car { public : virtual void drive () { } };class benz : public car { public : virtual void drive () override { } };int main () { return 0 ; }
没加之后的报错
重载,覆盖,隐藏的对比 重载:
两个函数在同一个作用域
函数名相同,参数不同,返回值没有要求
覆盖: 3. 两个函数分别在基类和派生类中的作用域中 4. 函数名/参数/返回值,都相同(协变除外) 5. 两个函数必须是虚函数
重定义(隐藏) 6. 一个在基类一个在派生类 7. 函数名相同 8. 不构成重写就是隐藏
抽象类 在虚函数的后面加上=0,这个函数就叫做纯虚函数, 包含纯虚函数的类就叫做抽象类,不能实例化出对象, 派生类继承以后,也不能实例化出对象,只有子类重写这个虚函数,才能实例化出对象 ,
底层剖析 我们会发现子类和父类是不一样的虚表,下面继承的是重写的虚函数(覆盖) 父类对象是父类对象的虚函数,子类对象是子类对象的虚函数 重写是语法层面,覆盖是原理上 覆盖成我重写的虚函数,
多态为什么必须是指针和引用呢? 指针和引用,虚表就是各自的,而用对象,那么虚表都是父类的,无法调用子类的,切片没有办法把虚表指针拷过去
多态的原理: 基类的指针/引用,指向谁就去谁的虚函数表中找到对应的虚函数进行调用,在父类里面找到父类的虚函数,在子类里面找到子类的虚函数,
同类型的对象,他的虚表指针是一样的,指向同一张虚表,都是指向它的 普通函数和虚函数都存储在代码段,只不过虚函数要把地址存一份到虚表,方便实现多态,
多态就算是私有的也可以调用,因为它是从虚表里面去找的,所以没有影响,有了虚函数这一个,私有也都是可以调用的,
总结:
派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员
基类b对象,和派生类d对象虚表是不一样的,这里我们发现func完成了重写,覆盖是指虚表中虚函数的覆盖,重写是语法上的 叫法,覆盖是原理层的叫法
子类继承下来没有重写的一部分,地址还是和父类的虚函数一样
子类也写了虚函数,这个虚函数也会在需表里面,只不过我们不好看
虚函数表在代码端(OS)/常量区(c语言)里面 虚表里面存的地址不是函数真正的地址,而是要跳几次才可以跳到目的函数
多继承中的虚表 打印虚表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 class Base {public : virtual void func1 () { cout << "base1::func1" << endl; } virtual void func2 () { cout << "base1::func2" << endl; }private : int a; };class driver : public Base {public : virtual void func1 () { cout << "driver func1" << endl; } virtual void func3 () { cout << "deriver func3" << endl; } void func4 () { cout << "derive func4" << endl; }private : int b; };typedef void (*vfptr) () ;void printvftable (vfptr table[]) { for (int i=0 ;table[i]!=nullptr ;i++) { printf ("vft[%d] :%p\n" ,i,table[i]); vfptr f=table[i]; f (); } cout<<endl; }int main () { person mike; student jason; func (mike); func (jason); person &r1 = jason; person r2 = jason; student s1; student s2; derive d; Base b; printvftable ((vfptr*)(*(void **)&b)); driver s; printvftable ((vfptr*)(*(void **)&s)); return 0 ; }
打印成功