C++對(duì)象模型
首先來(lái)說(shuō)一說(shuō)class的本質(zhì):
- class是一種特殊的struct
- 在內(nèi)存中class依舊可以看作變量的集合
- class與struct遵循相同的內(nèi)存對(duì)齊規(guī)則
- class中的成員函數(shù)與成員變量是分開存放的
- 每個(gè)對(duì)象有獨(dú)立的成員變量
- 所有對(duì)象共享類中的成員函數(shù)
然后再來(lái)看一個(gè)問(wèn)題:
class A
{
int i;
int j;
char c;
double d;
};
struct B
{
int i;
int j;
char c;
double d;
};
有上述1個(gè)類A、一個(gè)結(jié)構(gòu)體B,請(qǐng)問(wèn)執(zhí)行下列語(yǔ)句輸出多少?
sizeof(A) = ?
sizeof(B) = ?
最終輸出結(jié)果為:
sizeof(A) = 24
sizeof(B) = 24
再舉一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
class A
{
int i;
int j;
char c;
double d;
public:
void print()
{
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
A a;
cout << "sizeof(A) = " << sizeof(A) << endl; // 20 bytes
cout << "sizeof(a) = " << sizeof(a) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl; // 20 bytes
a.print();
//用B指針類型指向類A的對(duì)象
B* p = reinterpret_cast<B*>(&a);
//通過(guò)指針修改A對(duì)象的各個(gè)值
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3;
a.print();
p->i = 100;
p->j = 200;
p->c = 'C';
p->d = 3.14;
a.print();
return 0;
}
最終輸出結(jié)果為:
sizeof(A) = 24
sizeof(a) = 24
sizeof(B) = 24
i = 0, j = 1, c = , d = 2.26503e-314
i = 1, j = 2, c = c, d = 3
i = 100, j = 200, c = C, d = 3.14
從輸出結(jié)果結(jié)合代碼可以看出:
1、 類A 結(jié)構(gòu)體B 內(nèi)存大小是一樣的
2、在定義類A的對(duì)象后,未初始化時(shí),輸出成員變量都是未初始化的值
3、使用結(jié)構(gòu)體B的指針指向類A對(duì)象時(shí),可以直接修改成員變量的值
分析得出:
- 運(yùn)行時(shí)的類對(duì)象退化為結(jié)構(gòu)體的形式
- 所有成員變量在內(nèi)存中依次排布
- 成員變量間可能存在內(nèi)存空隙
- 可以通過(guò)內(nèi)存地址直接訪問(wèn)成員變量
- 訪問(wèn)權(quán)限關(guān)鍵字在運(yùn)行時(shí)失效
- 類中的成員函數(shù)位于代碼段中
- 調(diào)用成員函數(shù)時(shí)對(duì)象地址作為參數(shù)隱式傳遞
- 成員函數(shù)通過(guò)對(duì)象地址訪問(wèn)成員變量
- C++語(yǔ)法規(guī)則隱藏了對(duì)象地址的傳遞過(guò)程
繼承對(duì)象模型
- 在C++編譯器的內(nèi)部 類可以理解為結(jié)構(gòu)體
- 子類是由父類成員疊加子類新成員得到的
class Derived : public Demo
{
int mk;
};
Demo => int mi; <= Derived
Demo => int mj; <= Derived
int mk; <= Derived
首先來(lái)看一個(gè)例子:
#include <iostream>
#include <string>
using namespace std;
//類Demo
class Demo
{
//子類可以直接訪問(wèn)
protected:
int mi;
int mj;
public:
//虛函數(shù)print
virtual void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
//類Derived 繼承Demo
class Derived : public Demo
{
//子類中的成員變量mk
int mk;
public:
//帶參構(gòu)造函數(shù)
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
//子類成員函數(shù)print
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
//結(jié)構(gòu)體Test
struct Test
{
//包含1個(gè)指針 3個(gè)int類型變量
//這個(gè)指針是指向虛函數(shù)表的指針
//虛函數(shù)表指針位于對(duì)象最開始的位置
void* p;
int mi;
int mj;
int mk;
};
int main()
{
//輸出Demo類和Derived類
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
//定義并初始化d
Derived d(1, 2, 3);
//使用結(jié)構(gòu)體Test指針指向Derived類對(duì)象
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
//輸出成員變量以及父類的成員變量
d.print();
//直接通過(guò)p指針來(lái)賦值d的成員變量
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
//輸出各成員變量
d.print();
return 0;
}
輸出結(jié)果如下:
sizeof(Demo) = 16
sizeof(Derived) = 24
Before changing ...
mi = 1, mj = 2, mk = 3
After changing ...
mi = 10, mj = 20, mk = 30
在這里說(shuō)明一下,由于是在64位系統(tǒng)下運(yùn)行,所以指針占8個(gè)字節(jié)。Demo占16個(gè)字節(jié),是包含了2個(gè)4字節(jié)的int變量,加上1個(gè)8字節(jié)指向虛函數(shù)表的指針,共16個(gè)字節(jié)。Derived占24個(gè)字節(jié)是因?yàn)閮?nèi)存對(duì)齊,直接給int類型的mk變量分配8個(gè)字節(jié),所以共24個(gè)字節(jié)。使用Test結(jié)構(gòu)體指向Derived類對(duì)象時(shí),要注意指向虛函數(shù)表的指針是位于對(duì)象內(nèi)存最開始的位置,void * p就是這個(gè)指針,其余的按順序排列。
- C++多態(tài)的實(shí)現(xiàn)原理
- 當(dāng)類中聲明虛函數(shù)時(shí),編譯器會(huì)在類中生成一個(gè)虛函數(shù)表
- 虛函數(shù)表是一個(gè)存儲(chǔ)成員函數(shù)地址的數(shù)據(jù)結(jié)構(gòu)
- 虛函數(shù)表是由編譯器自動(dòng)生成與維護(hù)的
- virtual成員函數(shù)會(huì)被編譯器放入虛函數(shù)表中
- 存在虛函數(shù)時(shí),每個(gè)對(duì)象中都有一個(gè)指向虛函數(shù)表的指針
當(dāng)有Demo類如下:
class Demo
{
int mi, mj;
public:
virtual int add(int value)
{
return mi + mj + value;
}
};
此時(shí)這個(gè)對(duì)象就會(huì)有一個(gè)指針,指向編譯器維護(hù)的虛函數(shù)表。虛函數(shù)表中保存這成員函數(shù)的地址:
VTABLE => void Demo :: add(int value)
當(dāng)有Derived類繼承Demo類時(shí),如下:
class Derived : public Demo
{
int mk;
public:
virtual int add(int value)
{
return mk + value;
}
};
此時(shí)這個(gè)對(duì)象也會(huì)有一個(gè)指針,指向編譯器維護(hù)的虛函數(shù)表。虛函數(shù)表中保存這成員函數(shù)的地址:
VTABLE => void Derived :: add (int value)
當(dāng)執(zhí)行這個(gè)函數(shù)時(shí):
void run(Demo * p, int v)
{
p -> add(v);
}
執(zhí)行時(shí),編譯器會(huì)確認(rèn) add( ) 是否為虛函數(shù)?
1、Yes -> 編譯器在對(duì)象 VPTR 所指向的虛函數(shù)表中查找 add ( ) 的地址
2、No -> 編譯器直接可以確定被調(diào)成員函數(shù)的地址
所以,調(diào)用順序如下:
p -> VPTR -> VTABLE void (*pAdd)(int value) -> 0xFF010203
調(diào)用效率對(duì)比: 虛函數(shù) < 普通成員函數(shù)