面向对象的三大特征:
封装,多态,继承
前面我们已经讲了继承的一些知识点,在这基础上,我们讲的时候再涉猎一些多态的只是。
下面我们先接着上次讲有虚函数的菱形虚继承
前面我们已经讲了继承的一些知识点,在这基础上,我们讲的时候再涉猎一些多态的只是。
下面我们先接着上次讲有虚函数的菱形虚继承
首先什么是虚函数。?
虚函数:在类里面,函数前面有virtual关键字的成员函数就是虚函数。
代码块:
代码块:
class base { public: base() { cout << "base()" << endl; } ~base() { cout << "~base()" << endl; } virtual void dis()//加了关键字virtual,dis就成了虚函数 { cout << "pub=" << pub; cout << " pro=" << pro; cout << " pri=" << pri << endl; } int pub; protected: int pro; private: int pri; };
我们先讲几个知识点:
1.虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。
2.在一个类中, 成员函数有虚函数的话,那么,这个对象的前四个字节一定是存放一个指向这个虚函数表(简称虚表) 的指针。虚表里面放的是虚函数的地址
3.对于菱形继承这样,有 多个基类的类对象,则会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。
4.即使是存在虚基类指针, 虚表指针也是在虚基类指针的上方,这是为了保证正确取到虚函数的偏移量。
1.虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。
2.在一个类中, 成员函数有虚函数的话,那么,这个对象的前四个字节一定是存放一个指向这个虚函数表(简称虚表) 的指针。虚表里面放的是虚函数的地址
3.对于菱形继承这样,有 多个基类的类对象,则会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。
4.即使是存在虚基类指针, 虚表指针也是在虚基类指针的上方,这是为了保证正确取到虚函数的偏移量。
举个简单的例子
代码块:
代码块:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<stdio.h> using namespace std; class base { public: base(int a=1,int b=2,int c=3) : pub(a) , pro(b) , pri(c) { cout << "base()" << endl << endl; } ~base() { cout << "~base()" << endl << endl; } virtual void dis()//加了关键字virtual,dis就成了虚函数 { cout << "dis()" << endl<<endl; cout << "pub=" << pub; cout << " pro=" << pro; cout << " pri=" << pri << endl << endl; } virtual void func()//同上,虚函数 { pub *= 2; pro *= 2; pri *= 2; } int pub; protected: int pro; private: int pri; }; void Test()//析构函数只在函数体结束时候调用,所以在main函数里声明不好看到析构函数 { base b; cout << "sizeof(b)=" << sizeof(b) << endl << endl; } int main() { Test(); system("pause"); return 0; }
运行结果:
分析:
在成员函数dis(),func()没有加上virtual之前,b对象的大小等于12
加上virtual之后输出size=16,说明多出了四个字节。
在调用监视和内存查看,在对象最开始的四个字节确实是多了一个_vfptr指针
我们又在内存窗口输入_vfptr的地址0x00F9DC74,发现里面的两个地址确实和dis(),func()一一对应,也印证了我们上面的观点。
在成员函数dis(),func()没有加上virtual之前,b对象的大小等于12
加上virtual之后输出size=16,说明多出了四个字节。
在调用监视和内存查看,在对象最开始的四个字节确实是多了一个_vfptr指针
我们又在内存窗口输入_vfptr的地址0x00F9DC74,发现里面的两个地址确实和dis(),func()一一对应,也印证了我们上面的观点。
上一篇文章我们讲了没有虚函数的菱形继承和虚继承。
下面我们在观察一下 带虚函数的菱形虚继承的对象模型是怎样的
这里有两种情况:
1.ABCD四个类的虚函数都是virtual void dis(),谁也不多谁也不少。
代码块:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函数指针 class A { public: A(int x = 1) :a(x) { cout << "A类构造函数----A()" << endl << endl; } ~A() { cout << "A类析造函数----~A()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "a=" << a << endl; } int a;//把a放在公有位置,便于类外访问 }; class B :virtual public A//加上了关键字virtual,变成了虚继承 { public: B(int y = 2) :b(y) { cout << "B类构造函数----B()" << endl << endl; } ~B() { cout << "B类析构函数----~B()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "b=" << b << endl; } private: int b; }; class C :virtual public A//加上了关键字virtual,变成了虚继承 { public: C(int z = 3) :c(z) { cout << "C类构造函数----C()" << endl << endl; } ~C() { cout << "C类析构函数----~C()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "c=" << c << endl; } private: int c; }; class D :public B, public C//分别公有继承B,C { public: D(int z1 = 4) :d(z1) { cout << "D类构造函数------D()" << endl << endl; } ~D() { cout << "D类析构函数------~D()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "d=" << d << endl; } private: int d; }; void Test() { D obj; } int main() { Test(); system("pause"); return 0; }
分析:
它有一个虚基类A,所以存在虚基表指针;
它每个类还有虚函数,所以存在,虚表指针;
虚表指针位于虚基表指针上方(这个第二种情况解释) ;
但是这里比较特殊的就是每个类中的虚函数都是一样的,所以就构成了这种特殊的对象模型。
在这里我们通过调用监视和查看内存来观察:
截图:
通过上图我们知道,要是
每个类都有一样的虚函数,则虚表指针就不会存在与每一个对象中,而是只在虚基类的前四个字节,然后这个指针就指向了存放着统一的虚函数的虚表。
2.这种情况就是每个类的虚函数存在着不同。
代码块:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<iostream> using namespace std; typedef void(*FUNC) ();//函数指针 class A { public: A(int x = 1) :a(x) { cout << "A类构造函数----A()" << endl << endl; } ~A() { cout << "A类析造函数----~A()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "A::dis()" << endl; } int a;//把a放在公有位置,便于类外访问 }; class B :virtual public A//加上了关键字virtual,变成了虚继承 { public: B(int y = 2) :b(y) { cout << "B类构造函数----B()" << endl << endl; } ~B() { cout << "B类析构函数----~B()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "B::dis()" << endl; } virtual void func()//和D类相同的虚函数func() { cout << "B::func()" << endl; } virtual void func2()//B类自己的虚函数 { cout << "B::func2()" << endl; } int b; }; class C :virtual public A//加上了关键字virtual,变成了虚继承 { public: C(int z = 3) :c(z) { cout << "C类构造函数----C()" << endl << endl; } ~C() { cout << "C类析构函数----~C()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "C::dis()" << endl; } virtual void func3()//C类自己的虚函数 { cout << "C::func3()" << endl; } int c; }; class D :public B, public C//分别公有继承B,C { public: D(int z1 = 4) :d(z1) { cout << "D类构造函数------D()" << endl << endl; } ~D() { cout << "D类析构函数------~D()" << endl << endl; } virtual void dis()//记住这个dis(),每个类都有这个虚函数 { cout << "d::dis()" << endl; } virtual void func()//和B类相同的虚函数func() { cout << "D::func()" << endl; } virtual void func4()//D类自己的虚函数 { cout << "D::func4()" << endl; } int d; }; void PrintVTable(int* VTable) { cout << " 虚表地址-->" << VTable << endl; for (int i = 0; VTable[i] != 0; ++i) { printf(" 第%d个虚函数地址 :0X%x,-->", i, VTable[i]); FUNC f = (FUNC)VTable[i];//函数指针 f();//执行这个函数 } cout << endl; } int main() { D obj; A obj_a; B obj_b; C obj_c; int* VTable1 = (int*)(*(int*)&obj);//obj的地址,通过一系列的类型转换,所以指针这块要熟 PrintVTable(VTable1); int* VTable2 = (int*)(*(int*)&obj+32);//obj_a的地址,我是知道对象模型然后把代码写死了,你们可以尝试其它方法 PrintVTable(VTable2); int* VTable3 = (int*)(*(int*)&obj_b);//obj_b的地址 PrintVTable(VTable3); int* VTable4 = (int*)(*(int*)&obj_c);//obj_c的地址 PrintVTable(VTable4); cout << "sizeof (obj)=" << sizeof(obj) << endl;//obj对象的大小,一开始以为是36,但看内存发现多了一串0占了四个字节 cout << "sizeof (obj_a)=" << sizeof(obj_a) << endl; cout << "sizeof (obj_b)=" << sizeof(obj_b) << endl; cout << "sizeof (obj_c)=" << sizeof(obj_c) << endl; system("pause"); return 0; }
运行结果加后期
截图1:
截图2:
模型草图:
分析:
先了解一下多态:
概念:多态就是多种形态,C++的 多态分为静态多态和动态多态。
先了解一下多态:
概念:多态就是多种形态,C++的 多态分为静态多态和动态多态。
1. 静态多态:主要通过函数的重载和模板来实现,是在编译时决定,也叫编译时多态。
2. 动态多态:就是通过继承重写基类的虚函数实现的多态,是在运行时决定,也叫运行时多态。
2. 动态多态:就是通过继承重写基类的虚函数实现的多态,是在运行时决定,也叫运行时多态。
结合上一个例子模型的分析,这里的不同就是每个类里面的虚函数不一样了,所以它的对象模型也发生了改变,通过内存窗口相信大家也大概知道了是怎样的一个布局。
很多东西在截图上面都画出来了,所以我也不知道该怎么再分析了
相信大家通过自己动手去实现一遍,会有更加深刻的了解。。
相信大家通过自己动手去实现一遍,会有更加深刻的了解。。