一、虚函数
1.重载,重写(覆盖),重定义(隐藏)的对比
重载: 两个函数在一个作用域,函数名相同,参数不同 . 重写(覆盖): 两个函数分别在基类和派生类的作用域 函数名,返回类型,参数都必须相同(协变例外),两个函数必须是虚函数 . 重定义(隐藏): 两个函数分别在基类和派生类的作用域 函数名相同,两个基类和派生类的同名函数不构成重写(覆盖)就是重定义
2.虚函数的重写
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 class person { public : void BuyTicket () { cout << "all" << endl; } }; class student : public person{ public : void BuyTicket () { cout << "half" << endl; } }; void Fun1 (person& p) { p.BuyTicket (); } void Fun2 (person* p) { p->BuyTicket (); } int main () { student s; person p; Fun1 (s); Fun1 (p); Fun2 (&s); Fun2 (&p); return 0 ; }
①协变
基类与派生类虚函数的返回值类型不同 却仍然可以构成重写
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 class A {};class B : public A{};class person { public : virtual person* BuyTicket () { cout << "all" << endl; return nullptr ; } }; class student : public person{ public : virtual student* BuyTicket () { cout << "half" << endl; return nullptr ; } }; void Fun1 (person& p) { p.BuyTicket (); } int main () { student s; person p; Fun1 (s); Fun1 (p); return 0 ; }
②析构函数的重写
基类与派生类的函数名不同 但仍然可以构成重写
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 class person { public : ~person () { cout << "~person" << endl; } }; class student : public person{ public : ~student () { cout << "~student" << endl; } }; int main () { person* p1 = new person; person* p2 = new student; delete p1; delete p2; return 0 ; }
③虚函数的一些点
虚函数有一个指针的大小 虚函数重写生效的只是内容,而不是参数
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 class A { public : virtual void func (int val = 1 ) { cout << "A->" << val << endl; } virtual void test () { func (); } }; class B : public A{ public : void func (int val = 0 ) { cout << "B->" << val << endl; } }; int main () { A* p = new B; p->test (); return 0 ; }
④抽象类(纯虚函数) 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 class car { public : virtual void drive () = 0 ; }; class benz : public car{ public : virtual void drive () { cout << "benz" << endl; } }; int main () { benz autoccar; return 0 ; }
纯虚函数的作用是
强迫重写 表示抽象的类型(现实中没有对应实体的)
二、两个关键字
1.final
修饰虚函数:可以阻止函数被重写,final无法对类中非虚函数进行修饰 修饰类: 可以阻止类被继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class person { public : virtual void BuyTicket () final { cout << "all" << endl; } }; class student : public person{ public : virtual void BuyTicket () { cout << "half" << endl; } }; class A final {}; class B : public A{};
2.override
可以检查出虚函数是否被成功重写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class A { public : void out () { cout << "111" << endl; } }; class B : public A{ public : virtual void out () override { cout << "222" << endl; } };
三、多态的原理
1.原理 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 class base { public : virtual void fun1 () { cout << "base:fun1()" << endl; } virtual void fun2 () { cout << "base:fun2()" << endl; } private : int _b = 1 ; }; class derive : public base{ public : virtual void fun1 () { cout << "derive:fun1()" << endl; } private : int _d = 1 ; }; void function1 (base& b) { b.fun1 (); } void function2 (base b) { b.fun1 (); } int main () { base b1; cout << sizeof (base) << endl; derive d1; function1 (b1); function1 (d1); return 0 ; }
类里面有一个虚表,虚表里面存的指针指向虚函数 重写了,子指针改变指向 没重写,父指哪,子指哪
1 2 3 4 监视窗口中的_vfptr就是 虚函数表指针:简称虚表指针 其本质是 指针数组(虚函数指针) 一般来说对象前八个字节存的虚表指针 虚表指针数组最后一个指针为0x0000000000000000 表示这个虚函数数组结束了
2.验证虚函数和虚表的位置都是在代码段
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 class base { public : virtual void fun1 () { cout << "base::fun1()" << endl; } virtual void fun2 () { cout << "base::fun2()" << endl; } virtual void fun3 () { cout << "base::fun3()" << endl; } private : int _b = 1 ; }; class derive : public base{ public : virtual void fun1 () { cout << "derive::fun1()" << endl; } private : int _d = 2 ; }; void test () { base b1; printf ("_vftptr:%p\n" , *(double *)&b1); printf ("_vftptr:%p\n" , *(long long *)&b1); int i = 0 ; int * p1 = &i; int * p2 = new int ; const char * p3 = "hello" ; printf ("栈变量:%p\n" , p1); printf ("堆变量:%p\n" , p2); printf ("代码段常量:%p\n" , p3); printf ("代码段函数地址:%p\n" , &base::fun3); printf ("代码段函数地址:%p\n" , test); } int main () { base b1; derive d1; test (); return 0 ; }
四、动静态绑定
静态的绑定:编译时确定函数地址 动态的绑定:运行时到虚表中找虚函数的地址
编译时 是指: 高级语言转换为汇编语言时(初次检查错误) 运行时 是指: 程序开始运行,开始占用cpu和内存
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 class base { public : virtual void fun1 () { cout << "base::fun1()" << endl; } private : int _b = 1 ; }; class derive : public base{ public : virtual void fun1 () { cout << "derive::fun1()" << endl; } private : int _d = 2 ; }; void f1 (int i) {}void f2 (double d) {}int main () { int i = 0 ; double d = 1.1 ; f1 (i); f2 (d); base* p = new base; p->fun1 (); p = new derive; p->fun1 (); return 0 ; }
五、设计函数展现_vftptr所有函数
1. 单继承展现虚表
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 class base { public : virtual void fun1 () { cout << "base::fun1()" << endl; } virtual void fun2 () { cout << "base::fun2()" << endl; } private : int b; }; class derive : public base{ public : virtual void fun1 () { cout << "derive::fun1()" << endl; } virtual void fun3 () { cout << "derive::fun3()" << endl; } virtual void fun4 () { cout << "derive::fun4()" << endl; } private : int d; }; typedef void (*VF_PTR) () ; void PrintVFTable (VF_PTR pTable[]) { for (size_t i = 0 ; pTable[i] != 0 ; ++i) { printf ("vftabel[%d]:%p->" , i, pTable[i]); VF_PTR f = pTable[i]; f (); } cout << endl; } int main () { base b; derive d; PrintVFTable ((VF_PTR*)(*(long long *)&b)); PrintVFTable ((VF_PTR*)(*(void **)&b)); PrintVFTable ((VF_PTR*)(*(long long *)&d)); PrintVFTable ((VF_PTR*)(*(void **)&d)); return 0 ; }
2.多继承展现虚表
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 class base1 { public : virtual void fun1 () { cout << "base::fun1()" << endl; } virtual void fun2 () { cout << "base::fun2()" << endl; } private : int b1; }; class base2 { public : virtual void fun1 () { cout << "base::fun1()" << endl; } virtual void fun2 () { cout << "base::fun2()" << endl; } private : int b2; }; class derive : public base1,public base2{ public : virtual void fun1 () { cout << "derive::fun1()" << endl; } virtual void fun3 () { cout << "derive::fun3()" << endl; } private : int d; }; using VF_PTR = void (*)(); void PrintVFTable (VF_PTR pTable[]) { for (size_t i = 0 ; pTable[i] != nullptr ; ++i) { printf ("vftabel[%d]:%p->" , i, pTable[i]); VF_PTR f = pTable[i]; f (); } cout << endl; } int main () { derive d; PrintVFTable (( VF_PTR*)(*(void **)&d)); PrintVFTable ((VF_PTR*)(*(void **)((char *)&d + sizeof (base1)))); return 0 ; }
子类独有的函数,存在第一个虚表中
关于函数指针数组指针的用法
第一种 typedef void(PFUNC)(); 第二种 using VF_PTR = void( )();
留下了一个问题
在64位平台下base1 和 base2的函数内部只要操作不同,就会导致 base2无法依次访问时遇见nullptr停止,但是从虚表开头开始遍历却没有问题 32位平台下并没有这种问题
六、继承和多态
1.几个关于继承和多态的点 1 2 3 4 5 1. inline 函数不可以是虚函数,inline 函数没有地址,无法把地址放到虚函数表中2. 静态成员不可以是虚函数,静态成员函数没有this 指针,使用 类型::成员函数 的调用方式无法访问虚表,所以静态成员函数无法放进虚函数表3. 构造函数不可以是虚函数,对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的4. 对象访问普通函数快还是虚函数快?如果是普通对象一样快; 如果是指针对象或者是引用对象,普通函数快,构成多态,运行时调用虚函数需要需要到虚表中去查找;
2.继承多态的大小 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 class A { public : A (int x = 1 ) { cout << "aa" << endl; } virtual void Aa () {} }; class B :virtual public A{ public : B (int x = 0 ) { cout << "bb" << endl; } virtual void Aa () {} }; int main () { B b; A a; cout << sizeof (B) << endl; printf ("%p\n" , &A::Aa); printf ("%p\n" , &B::Aa); return 0 ; }
3.一道题 计算输出结果
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 class A { public : A (const char * s) { cout << s << endl; } }; class B :virtual public A{ public : B (const char * s1, const char * s2) :A (s1) { cout << s2 << endl; } }; class C :virtual public A{ public : C (const char * s1, const char * s2) :A (s1) { cout << s2 << endl; } }; class D :public B, public C{ public : D (const char * s1,const char * s2,const char * s3,const char * s4) :B (s1, s2), C (s1, s3), A (s1) { cout << s4 << endl; } }; int main () { D* p = new D ("class A" , "class B" , "class C" , "class D" ); delete p; return 0 ; }
1.A只有一个,所以A只构造一次,优先A构造 2.初始化列表优先于函数体,D最后 3.构造按继承顺序,即使先C(s1,s3),B(s1,s2) 也是B先输出,C再输出 所以结果为ABCD