1,C++ 中继承是非常重要的一个特性,本节课研究在继承的情形下,C++ 的对象模 型又有什么不同;
2,继承对象模型(最简单的情况下):
1,在 C++ 编译器的内部类可以理解为结构体;
2,子类是由父类成员叠加子类新成员得到的;
1,代码示例:
1 class Derived : public Demo
2 {
3 int mk;
4 };
2,对象排布:
1,在对象模型中,先排布父类对象模型,再排布子类对象模型,见 本文3中内容;
3,继承对象模型初探编程实验:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Demo 7 { 8 protected: 9 int mi; 10 int mj; 11 public: 12 virtual void print() 13 { 14 cout << "mi = " << mi << ", " 15 << "mj = " << mj << endl; 16 } 17 }; 18 19 class Derived : public Demo 20 { 21 int mk; 22 public: 23 Derived(int i, int j, int k) 24 { 25 mi = i; 26 mj = j; 27 mk = k; 28 } 29 30 void print() 31 { 32 cout << "mi = " << mi << ", " 33 << "mj = " << mj << ", " 34 << "mk = " << mk << endl; 35 } 36 }; 37 38 struct Test 39 { 40 void* p; // 为了证明 C++ 编译器真的会在对象中塞入一个指针成员变量,且指针放在最开始的字节处; 41 int mi; 42 int mj; 43 int mk; 44 }; 45 46 int main() 47 { 48 cout << "sizeof(Demo) = " << sizeof(Demo) << endl; // 8 bytes 49 cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12 bytes 50 51 Derived d(1, 2, 3); 52 Test* p = reinterpret_cast<Test*>(&d); 53 54 cout << "Before changing ..." << endl; 55 56 d.print(); // mi = 1, mj = 2, mk = 3; 57 58 /* 通过 p 对象改变成员变量的值,这里加了 p 指针后任然能够成功的访问; */ 59 p->mi = 10; 60 p->mj = 20; 61 p->mk = 30; 62 63 cout << "After changing ..." << endl; 64 65 d.print(); // mi = 10, mj = 20, mk = 30;在外界访问不到的保护成员变量的值被改变了,改变是因为 d 对象的内存分布 Test 结构体的(此时类中未有虚函数,Test 中未有 空指针),因此可以用 p 指针改变 d 对象当中成员变量的值; 66 67 return 0; 68 }
4,多态对象模型:
1,C++ 多态的实现原理:
1,当类中声明虚函数时,编译器会在类中生成一个虚函数表;
2,虚函数表是一个存储成员函数地址的数据结构;
1,存储虚函数成员地址的数据结构;
3,虚函数表是由编译器自动生成与维护的;
4,virtual 成员函数会被编译器放入虚函数表中;
1,这个表是给对象使用的;
2,对象在创建时,在内部有一个虚函数表指针,这个指针指向虚函数表;
5,存在虚函数时,每个对象中都有一个指向虚函数表的指针;
2,框图展示:
1,框架一
1,编译父类时,编译器发现了 virtual 成员函数,因此编译器创建了一个虚函数表,并且将虚函数的地址放到了虚函数表里面;
2,编译子类时,继承自 Demo,编译器发现重写了 add 函数,因此必须是虚函数,于是编译器就为子类也生成一张虚函数表,并且也会在虚函数表中放入重写过后的 add 虚函数的地址;
2,框架二
1,当创建父类对象的时候,会为 Demo 对象自动的塞入一个指针 VPTR,也 就是如果类中有虚函数的话,在最终生成类对象的时候,会被编译器强 制赛一个指针成员变量,这个指针成员变量对于程序员是不可见的,但是它确确实实的会存在对象当中,这个指针成员变量指向了虚函数表;
2,当创建子类对象的时候,会为 Derived 对