C++多态深度解析
一.多态的概念
通过使用virtual关键字对多态进行支持,被virtual声明的函数被重写后具有多态特性,virtual声明的函数叫虚函数。
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Parent 7 { 8 public: 9 virtual void print() //使用virtual,变成虚函数(这个函数可能在子类被重写) 10 { 11 cout << "I'm Parent." << endl; 12 } 13 }; 14 15 class Child : public Parent 16 { 17 public: 18 void print() //由于继承关系,这里也是虚函数,只是可以省略不写 19 { 20 cout << "I'm Child." << endl; 21 } 22 }; 23 24 void how_to_print(Parent* p) 25 { 26 p->print(); // 展现多态的行为 27 } 28 29 int main() 30 { 31 Parent p; 32 Child c; 33 34 how_to_print(&p); // Expected to print: I'm Parent. 35 how_to_print(&c); // Expected to print: I'm Child. 36 37 return 0; 38 }
结果:
1 I'm Parent. 2 I'm Child.
子类中重写的函数将覆盖父类中的函数,虽然被重写覆盖了,但通过作用域分辨符(::)可以访问到父类中的函数。
二.虚函数与纯虚函数
纯虚函数和虚函数都是使用virtual关键字,只是纯虚函数在函数原型后面加上“=0”,纯虚函数没有实例化,虚函数有函数体,有实例化,定义纯虚函数是为了实现一个接口,起到一个规范的作用。
1 #include <iostream> 2 3 using namespace std; 4 5 class A{ 6 public: 7 virtual void f(const string& str)=0; 8 virtual void e() { 9 cout << "Base e() : " << endl; 10 } 11 }; 12 // 缺省实现,纯虚函数可以有函数体,但必须类的外部定义,如果子类不想实现,可以调用基类的 13 void A::f(const string& str) { 14 cout << "A f()" << str << endl; 15 } 16 17 class B : public A{ 18 public: 19 // 本质上还是重写了,只不过直接调用了基类的函数实现 20 virtual void f(const string& str){ 21 cout << "Call A::f " << endl; 22 A::f(str); 23 } 24 void e(){ 25 cout << "B e()" << endl; 26 } 27 }; 28 29 int main() 30 { 31 B b; 32 b.f("hello world"); 33 b.e(); 34 A *a = new B(); 35 // 都是多态 36 a->f("hello"); 37 a->e(); 38 return 0; 39 }
三.对象的内存布局
对象的内存布局包括两方面:成员变量的内存,指向虚函数表指针的内存,静态变量与成员函数是不占用对象内存的。
空类的大小为:1
1 #include <iostream> 2 using namespace std; 3 4 class NoMembers 5 { 6 }; 7 8 int main() 9 { 10 NoMembers n; // Object of type NoMembers. 11 cout << "The size of an object of empty class is: " 12 << sizeof(n) << endl; 13 }
含有虚函数成员的类大小为4:含有虚函数的类需要维护一个虚函数表,有一个指向虚函数表的指针.
1 class Base { 2 3 public: 4 5 virtual void f() { cout << "Base::f" << endl; } 6 7 virtual void g() { cout << "Base::g" << endl; } 8 9 virtual void h() { cout << "Base::h" << endl; } 10 11 };
多继承下类的大小
1 #include<iostream> 2 using namespace std; 3 4 5 class A 6 { 7 }; 8 9 class B 10 { 11 char ch; 12 virtual void func0() { } 13 }; 14 15 class C 16 { 17 char ch1; 18 char ch2; 19 virtual void func() { } 20 virtual void func1() { } 21 }; 22 23 class D: public A, public C 24 { 25 int d; 26 virtual void func() { } 27 virtual void func1() { } 28 }; 29 class E: public B, public C 30 { 31 int e; 32 virtual void func0() { } 33 virtual void func1() { } 34 }; 35 36 int main(void) 37 { 38 cout<<"A="<<sizeof(A)<<endl; //1 39 cout<<"B="<<sizeof(B)<<endl; //4+4=8 40 cout<<"C="<<sizeof(C)<<endl; //4+4=8 41 cout<<"D="<<sizeof(D)<<endl; //8+4=12 42 cout<<"E="<<sizeof(E)<<endl; //8+4=12 43 return 0; 44 }
虚继承的情况
1 class A { 2 3 int a; 4 5 }; 6 7 class B:virtual public A{ 8 9 virtual void myfunB(){} 10 11 }; 12 13 class C:virtual public A{ 14 15 virtual void myfunC(){} 16 17 }; 18 19 class D:public B,public C{ 20 21 virtual void myfunD(){} 22 23 };
以上代码中sizeof(A)=4,,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16。
B,C中由于是虚继承因此大小为int大小,加指向虚基类的指针的大小,加指向虚函数表指针的大小。
四.class退化成了struct,他们内存布局一样
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class A //大小 7 { 8 int i; //4 9 int j; //4 10 char c; //1 + 3(补3) 11 double d; //8 12 public: 13 void print() 14 { 15 cout << "i = " << i << ", " 16 << "j = " << j << ", " 17 << "c = " << c << ", " 18 << "d = " << d << endl; 19 } 20 }; 21 22 struct B //大小 23 { 24 int i; //4 25 int j; //4 26 char c; //1 + 3(补3) 27 double d; //8 28 }; 29 30 int main() 31 { 32 A a; 33 34 cout << "sizeof(A) = " << sizeof(A) << endl; // 20 bytes 35 cout << "sizeof(a) = " << sizeof(a) << endl; 36 cout << "sizeof(B) = " << sizeof(B) << endl; // 20 bytes 37 38 a.print(); 39 //p指向了a的地址 40 B* p = reinterpret_cast<B*>(&a); //a不是整数。&a变成地址后进行类型转换 41 /* 42 reinterpret_cast强制类型转换 43 用于指针类型间的强制转换 44 用于整数和指针类型间的强制转换(类指针也是指针) 45 */ 46 47 p->i = 1; 48 p->j = 2; 49 p->c = 'c'; 50 p->d = 3; 51 52 a.print(); 53 54 p->i = 100; 55 p->j = 200; 56 p->c = 'C'; 57 p->d = 3.14; 58 59 a.print(); 60 61 return 0; 62 }
五.多继承方式会产生多个虚函数表
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class BaseA 7 { 8 public: 9 virtual void funcA() 10 { 11 cout << "BaseA::funcA()" << endl; 12 } 13 }; 14 15 class BaseB 16 { 17 public: 18 virtual void funcB() 19 { 20 cout << "BaseB::funcB()" << endl; 21 } 22 }; 23 24 class Derived : public BaseA, public BaseB 25 { 26 27 }; 28 29 int main() 30 { 31 Derived d; 32 BaseA* pa = &d; //pa->BaseA 33 BaseB* pb = &d; //pb->BaseB 34 BaseB* pbe = (BaseB*)pa; // oops!!指向了A的虚函数表(重点在这里) 35 BaseB* pbc = dynamic_cast<BaseB*>(pa); //指向了B的虚函数表 36 37 cout << "sizeof(d) = " << sizeof(d) << endl; //8,两个指针,指向虚函数表 38 39 cout << "Using pa to call funcA()..." << endl; 40 41 pa->funcA(); //BaseA::funcA() 42 43 cout << "Using pb to call funcB()..." << endl; 44 45 pb->funcB(); //BaseB::funcB() 46 47 cout << "Using pbc to call funcB()..." << endl; 48 49 pbc->funcB(); //BaseB::funcB() 50 51 cout << endl; 52 53 cout << "pa = " << pa << endl; //假设:A 54 cout << "pb = " << pb << endl; //假设:B 55 cout << "pbe = " << pbe << endl; 56 cout << "pbc = " << pbc << endl; 57 58 return 0; 59 }
结果:
1 sizeof(d) = 8 2 Using pa to call funcA()... 3 BaseA::funcA() 4 Using pb to call funcB()... 5 BaseB::funcB() 6 Using pbc to call funcB()... 7 BaseB::funcB() 8 9 pa = 0xbf9d2558 10 pb = 0xbf9d255c 11 pbe = 0xbf9d2558 12 pbc = 0xbf9d255c
强制转换后(Base* pbb = (Base*)pa),pbb是BaseB类型,应该指向vptr2的虚函数表,但却指向了vptr1的虚函数表 。
这时需要进行强制类型转换时,C++中推荐使用新式类型转换关键字(dynamic_cast)!!,BaseB* pbc = dynamic_cast<BaseB*>(pa),指向了B的虚函数表。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通