C++深度解析 C++對象模型分析(下) --- 繼承對象模型,多態對象模型,虛函數表,用C語言實現多態(50)
繼承對象模型
在C++編譯器的內部類可以理解爲結構體。
子類是由 父類成員 疊加 子類新成員 得到的。
代碼如下:繼承對象模型初探
#include <iostream>
#include <string>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
};
class Derived : public Demo
{
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl; // 8
cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12
Derived d(1, 2, 3);
//把一個指針類型重新解釋爲另一個指針類型
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
d.print();
//通過指針p改變d對象成員變量的值
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
return 0;
}
結果如下:
分析:
成員函數存放在代碼段,實際對象只包含成員變量。
繼承對象模型,父類成員變量疊加子類新定義成員變量,父類成員變量排在前面,子類新定義成員變量排在後面。
多態對象模型
多態與虛函數的區別:
多態:面向對象理論中的一個概念,相同的行爲方式,不同的行爲結果,表現多種形態。
虛函數:多態的表現形式由虛函數實現。
C++多態的實現原理
- 當類中聲明虛函數時,編譯器會在類中生成一個虛函數表
- 虛函數表是一個存儲成員函數地址的數據結構
- 虛函數是由編譯器自動生成與維護的
- virtual成員函數會被編譯器放入虛函數表中
- 存在虛函數時,每個對象中都有一個指向虛函數表的指針
void run(Demo* p, int v)
{
p->add(v);
}
編譯器確認run()是否爲虛函數:
如果run是虛函數,編譯器在對象VPTR所指向的虛函數表中查找add()的地址
如果run不是虛函數,編譯器直接可以確定被調用成員函數的地址
代表如下:
#include <iostream>
#include <string>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
public:
//裏面存在一個虛函數表指針
virtual void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo
{
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
void *p; //指向虛函數表的指針
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
//把一個指針類型重新解釋爲另一個指針類型
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
d.print();
//通過指針p改變d對象成員變量的值
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
return 0;
}
結果如下:
分析:
當創建對象時,如果類裏面有虛函數,最終生成對象時,會被編譯器強行塞入一個指針成員變量,這個指針成員變量是不可見的,但是它確實存在,這個指針成員變量指向虛函數表。
用C語言實現面向對象,用C語言實現多態
代碼如下:
50-2.h
#ifndef _50_2_H_
#define _50_2_H_
typedef void Demo;
typedef void Derived;//子類
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
//子類成員函數
Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);//虛函數
#endif
50-2.c
#include "50-2.h"
#include "malloc.h"
//定義一個全局的虛函數,這個函數在當前文件能訪問
static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);
//用結構體來表示虛函數表
struct VTable //2. 定義虛函數表數據結構
{
//函數指針
int (*pAdd)(Derived*, int); //3. 虛函數表裏面存儲什麼???
};
struct ClassDemo
{
struct VTable* vptr;//1. 定義虛函數表指針 ==》 虛函數表指針類型???
int mi;
int mj;
};
//父類的成員變量疊加子類的成員變量
struct ClassDerived
{
//最開始的部分爲父類
struct ClassDemo d;
int mk;
};
//虛函數表變量,用送她題材
static struct VTable g_Demo_vtbl =
{
Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl =
{
Derived_Virtual_Add
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL )
{
//指向虛函數表
ret->vptr = &g_Demo_vtbl; //4. 關聯對象和虛函數表
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
//6. 定義虛函數表中指針所指向的具體函數
static int Demo_Virtual_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
//5. 分析具體的虛函數!!!!
int Demo_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
//通過對象,找到虛函數表的指針,然後在虛函數表中找到具體調用函數的地址
return obj->vptr->pAdd(pThis, value);
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
//子類構造函數
Derived* Derived_Create(int i, int j, int k)
{
struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
if( ret != NULL )
{
ret->d.vptr = &g_Derived_vtbl;
//初始化父類
ret->d.mi = i;
ret->d.mj = j;
//初始化子類
ret->mk = k;
}
return ret;
}
int Derived_GetK(Derived* pThis)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk;
}
static int Derived_Virtual_Add(Demo* pThis, int value)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk + value;
}
int Derived_Add(Derived* pThis, int value)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->d.vptr->pAdd(pThis, value);
}
main.c
#include "stdio.h"
#include "50-2.h"
void run(Demo* p, int v)
{
int r = Demo_Add(p, 3);
printf("r = %d\n", r);
}
int main()
{
//創建父類對象
Demo* pb = Demo_Create(1, 2);
//創建子類對象
Derived* pd = Derived_Create(1, 22, 333);
printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
run(pb, 3);
run(pd, 3);
Demo_Free(pb);
Demo_Free(pd);
return 0;
}
結果如下:
小結:
繼承的本質就是父子間成員變量的疊加
C++中的多態是通過虛函數表實現的
虛函數表是由編譯器自動生成與維護的
虛函數的調用效率低於普通成員函數