C++的程序員對C中的字符串指針操作的函數(shù)卻并不是相當(dāng)?shù)氖煜ぁ6鳦中的這些字符串的指針操作函數(shù)有的時(shí)候也是必須要面對的,比如我們的庫要提供
C函數(shù)接口,保持向后兼容和跨平臺,還有我們經(jīng)常使用一些第三方的庫中都或多或少的使用到了這些C中的指針操作函數(shù),所以下面列出C的指針操作函數(shù),幫助
大家熟悉之。
感謝,Thanks!
四 堆棧與函數(shù)調(diào)用
一 C++程序內(nèi)存分配
1) 在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲單元都在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲單元自動被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,一般使用寄存器來存取,效率很高,但是分配的內(nèi)存容量有限。
2) 從堆上分配,亦稱動態(tài)內(nèi)存分配。程序在運(yùn)行的時(shí)候用malloc或new申請任意多少的內(nèi)存,程序員自己負(fù)責(zé)在何時(shí)用free或delete來釋放內(nèi)存。動態(tài)內(nèi)存的生存期由程序員自己決定,使用非常靈活。
3) 從靜態(tài)存儲區(qū)域分配。內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在。例如全局變量,static變量。
4) 文字常量分配在文字常量區(qū),程序結(jié)束后由系統(tǒng)釋放。
5)程序代碼區(qū)。
經(jīng)典實(shí)例:(代碼來自網(wǎng)絡(luò)高手,沒有找到原作者)

Code
#include <string>
int a=0; //全局初始化區(qū)
char *p1; //全局未初始化區(qū)
void main()
{
int b;//棧
char s[]="abc"; //棧
char *p2; //棧
char *p3="123456"; //123456"0在常量區(qū),p3在棧上。
static int c=0; //全局(靜態(tài))初始化區(qū)
p1 = (char*)malloc(10);
p2 = (char*)malloc(20); //分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。
strcpy(p1,"123456"); //123456"0放在常量區(qū),編譯器可能會將它與p3所向"123456"0"優(yōu)化成一個(gè)地方。
}
二 三種內(nèi)存對象的比較
棧對象的優(yōu)勢是在適當(dāng)?shù)臅r(shí)候自動生成,又在適當(dāng)?shù)臅r(shí)候自動銷毀,不需要程序員操心;而且棧對象的創(chuàng)建速度一般
較堆對象快,因?yàn)榉峙涠褜ο髸r(shí),會調(diào)用operator new操作,operator
new會采用某種內(nèi)存空間搜索算法,而該搜索過程可能是很費(fèi)時(shí)間的,產(chǎn)生棧對象則沒有這么麻煩,它僅僅需要移動棧頂指針就可以了。但是要注意的是,通常棧
空間容量比較小,一般是1MB~2MB,所以體積比較大的對象不適合在棧中分配。特別要注意遞歸函數(shù)中最好不要使用棧對象,因?yàn)殡S著遞歸調(diào)用深度的增加,
所需的棧空間也會線性增加,當(dāng)所需棧空間不夠時(shí),便會導(dǎo)致棧溢出,這樣就會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
堆對象創(chuàng)建和銷毀都要由程序員負(fù)責(zé),所以,如果
處理不好,就會發(fā)生內(nèi)存問題。如果分配了堆對象,卻忘記了釋放,就會產(chǎn)生內(nèi)存泄漏;而如
果已釋放了對象,卻沒有將相應(yīng)的指針置為NULL,該指針就是所謂的“懸掛指針”,再度使用此指針時(shí),就會出現(xiàn)非法訪問,嚴(yán)重時(shí)就導(dǎo)致程序崩潰。但是高效
的使用堆對象也可以大大的提高代碼質(zhì)量。比如,我們需要?jiǎng)?chuàng)建一個(gè)大對象,且需要被多個(gè)函數(shù)所訪問,那么這個(gè)時(shí)候創(chuàng)建一個(gè)堆對象無疑是良好的選擇,因?yàn)槲覀?
通過在各個(gè)函數(shù)之間傳遞這個(gè)堆對象的指針,便可以實(shí)現(xiàn)對該對象的共享,相比整個(gè)對象的傳遞,大大的降低了對象的拷貝時(shí)間。另外,相比于棧空間,堆的容量要
大得多。實(shí)際上,當(dāng)物理內(nèi)存不夠時(shí),如果這時(shí)還需要生成新的堆對象,通常不會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,而是系統(tǒng)會使用虛擬內(nèi)存來擴(kuò)展實(shí)際的物理內(nèi)存。
靜態(tài)存儲區(qū)。所有的靜態(tài)對象、全局對象都于靜態(tài)存儲區(qū)分配。關(guān)于全局對象,是在main()函數(shù)執(zhí)行前就分配好了的。其實(shí),在main()函數(shù)中的顯示代
碼執(zhí)行之前,會調(diào)用一個(gè)由編譯器生成的_main()函數(shù),而_main()函數(shù)會進(jìn)行所有全局對象的的構(gòu)造及初始化工作。而在main()函數(shù)結(jié)束之
前,會調(diào)用由編譯器生成的exit函數(shù),來釋放所有的全局對象。比如下面的代碼:
void main(void)
{
… …// 顯式代碼
}
|
實(shí)際上,被轉(zhuǎn)化成這樣:
void main(void)
{
_main(); //隱式代碼,由編譯器產(chǎn)生,用以構(gòu)造所有全局對象
… … // 顯式代碼
… …
exit() ; // 隱式代碼,由編譯器產(chǎn)生,用以釋放所有全局對象
}
|
除了全局靜態(tài)對象,還有局部靜態(tài)對象通和class的靜態(tài)成員,局部靜態(tài)對象是在函數(shù)中定義的,就像棧對象一樣,只不過,其前面多了個(gè)
static關(guān)鍵字。局部靜態(tài)對象的生命期是從其所在函數(shù)第一次被調(diào)用,更確切地說,是當(dāng)?shù)谝淮螆?zhí)行到該靜態(tài)對象的聲明代碼時(shí),產(chǎn)生該靜態(tài)局部對象,直到
整個(gè)程序結(jié)束時(shí),才銷毀該對象。class的靜態(tài)成員的生命周期是該class的第一次調(diào)用到程序的結(jié)束。
三 函數(shù)調(diào)用與堆棧
1)編譯器一般使用棧來存放函數(shù)的參數(shù),局部變量等來實(shí)現(xiàn)函數(shù)調(diào)用。有時(shí)候函數(shù)有嵌套調(diào)用,這個(gè)時(shí)候棧中會有多個(gè)函數(shù)的信息,每個(gè)函數(shù)占用一個(gè)連續(xù)的區(qū)域。一個(gè)函數(shù)占用的區(qū)域被稱作幀(frame)。同時(shí)棧是線程獨(dú)立的,每個(gè)線程都有自己的棧。例如下面簡單的函數(shù)調(diào)用:

另外函數(shù)堆棧的清理方式?jīng)Q定了當(dāng)函數(shù)調(diào)用結(jié)束時(shí)由調(diào)用函數(shù)或被調(diào)用函數(shù)來清理函數(shù)幀,在VC中對函數(shù)棧的清理方式由兩種:
|
參數(shù)傳遞順序 |
誰負(fù)責(zé)清理參數(shù)占用的堆棧 |
__stdcall |
從右到左 |
被調(diào)函數(shù) |
__cdecl |
從右到左 |
調(diào)用者 |
2) 有了上面的知識為鋪墊,我們下面細(xì)看一個(gè)函數(shù)的調(diào)用時(shí)堆棧的變化:
代碼如下:

Code
int Add(int x, int y)
{
return x + y;
}
void main()
{
int *pi = new int(10);
int *pj = new int(20);
int result = 0;
result = Add(*pi,*pj);
delete pi;
delete pj;
}
對上面的代碼,我們分為四步,當(dāng)然我們只畫出了我們的代碼對堆棧的影響,其他的我們假設(shè)它們不存在,哈哈!
第一,int *pi = new int(10); int *pj = new int(20); int result = 0; 堆棧變化如下:

第二,Add(*pi,*pj);堆棧如下:

第三,將Add的結(jié)果給result,堆棧如下:

第四,delete pi; delete pj; 堆棧如下:

第五,當(dāng)main()退出后,堆棧如下,等同于main執(zhí)行前,哈哈!

四 完!
五 sizeof與內(nèi)存布局
有了前面幾節(jié)的鋪墊,本節(jié)開始摸索C++的對象的內(nèi)存布局,平臺為windows32位+VS2008。
一 內(nèi)置類型的size
內(nèi)置類型,直接上代碼,幫助大家加深記憶:

Code
void TestBasicSizeOf()
{
cout << __FUNCTION__ << endl;
cout << " sizeof(char)= " << sizeof ( char ) << endl;
cout << " sizeof(int)= " << sizeof ( int ) << endl;
cout << " sizeof(float)= " << sizeof ( float ) << endl;
cout << " sizeof(double)= " << sizeof ( double ) << endl;
cout << " sizeof('$')=" << sizeof ( '$' ) << endl;
cout << " sizeof(1)= " << sizeof ( 1 ) << endl;
cout << " sizeof(1.5f)= " << sizeof ( 1.5f ) << endl;
cout << " sizeof(1.5)= " << sizeof ( 1.5 ) << endl;
cout << " sizeof(Good!)= " << sizeof ( "Good!" ) << endl ;
char str[] = "CharArray!";
int a[10];
double xy[10];
cout << " char str[] = ""CharArray!""," << " sizeof(str)= " << sizeof (str) << endl;
cout << " int a[10]," << " sizeof(a)= " << sizeof (a) << endl;
cout << " double xy[10]," << " sizeof(xy)= " << sizeof (xy) << endl;
cout << " sizeof(void*)= " << sizeof(void*) << endl;
}
運(yùn)行結(jié)果如下:

二 struct/class的大小
在C++中我們知道struct和class的唯一區(qū)別就是默認(rèn)的訪問級別不同,struct默認(rèn)為public,而class的默認(rèn)為
private。所以考慮對象的大小,我們均以struct為例。對于struct的大小對于初學(xué)者來說還確實(shí)是個(gè)難回答的問題,我們就通過下面的一個(gè)
struct定義加逐步的變化來引出相關(guān)的知識。
代碼如下:

Code
struct st1
{
short number;
float math_grade;
float Chinese_grade;
float sum_grade;
char level;
}; //20
struct st2
{
char level;
short number;
float math_grade;
float Chinese_grade;
float sum_grade;
};//16
#pragma pack(1)
struct st3
{
char level;
short number;
float math_grade;
float Chinese_grade;
float sum_grade;
}; //15
#pragma pack()
void TestStructSizeOf()
{
cout << __FUNCTION__ << endl;
cout << " sizeof(st1)= " << sizeof (st1) << endl;
cout << " offsetof(st1,number) " << offsetof(st1,number) << endl;
cout << " offsetof(st1,math_grade) " << offsetof(st1,math_grade) << endl;
cout << " offsetof(st1,Chinese_grade) " << offsetof(st1,Chinese_grade) << endl;
cout << " offsetof(st1,sum_grade) " << offsetof(st1,sum_grade) << endl;
cout << " offsetof(st1,level) " << offsetof(st1,level) << endl;
cout << " sizeof(st2)= " << sizeof (st2) << endl;
cout << " offsetof(st2,level) " << offsetof(st2,level) << endl;
cout << " offsetof(st2,number) " << offsetof(st2,number) << endl;
cout << " offsetof(st2,math_grade) " << offsetof(st2,math_grade) << endl;
cout << " offsetof(st2,Chinese_grade) " << offsetof(st2,Chinese_grade) << endl;
cout << " offsetof(st2,sum_grade) " << offsetof(st2,sum_grade) << endl;
cout << " sizeof(st3)= " << sizeof (st3) << endl;
cout << " offsetof(st3,level) " << offsetof(st3,level) << endl;
cout << " offsetof(st3,number) " << offsetof(st3,number) << endl;
cout << " offsetof(st3,math_grade) " << offsetof(st3,math_grade) << endl;
cout << " offsetof(st3,Chinese_grade) " << offsetof(st3,Chinese_grade) << endl;
cout << " offsetof(st3,sum_grade) " << offsetof(st3,sum_grade) << endl;
}
運(yùn)行結(jié)果如下;

基于上面的對struct的測試,我們是不是有些驚呆哦,對于C++的初學(xué)者更是情不自禁的說:“我靠!原來順序不同所占空間都不同啊,還有那個(gè)
pack是啥東東啊?”,其實(shí)這里蘊(yùn)含了一個(gè)內(nèi)存對齊的問題,在計(jì)算機(jī)的底層進(jìn)行內(nèi)存的讀寫的時(shí)候,如果內(nèi)存對齊的話可以提高讀寫效率,下面是VC的默認(rèn)
規(guī)則:
1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2) 結(jié)構(gòu)體每個(gè)成員相對于結(jié)構(gòu)體首地址的偏移量(offset)都是成員大小的整數(shù)倍, 如有需要編譯器會在成員之間加上填充字節(jié)(internal adding);
3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個(gè)成員之后加上填充字節(jié)(trailing padding)。
當(dāng)然VC提供了工程選項(xiàng)/Zp[1|2|4|8|16]可以修改對齊方式,當(dāng)然我們也可以在代碼中對部分類型實(shí)行特殊的內(nèi)存對齊方式,修改方式為#pragma pack( n ),n為字節(jié)對齊
數(shù),其取值為1、2、4、8、16,默認(rèn)是8,取消修改用#pragma pack(),如果結(jié)構(gòu)體某成員的sizeof大于你設(shè)置的,則按你的設(shè)置來對齊。
三 struct的嵌套
1)實(shí)例:

Code
struct A
{
int i;
char c;
double d;
short s;
}; // 24
struct B
{
char cc;
A a;
int ii;
}; // 40
布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

2)實(shí)例:

Code
#pragma pack(4)
struct A2
{
int i;
char c;
double d;
short s;
}; // 20
#pragma pack()
struct B2
{
char cc;
A2 a;
int ii;
}; // 28
布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

總結(jié):
由于結(jié)構(gòu)體的成員可以是復(fù)合類型,比如另外一個(gè)結(jié)構(gòu)體,所以在尋找最寬基本類型成員時(shí),應(yīng)當(dāng)包括復(fù)合類型成員的子成員,而不是把復(fù)合成員看成是一個(gè)整體。但在確定復(fù)合類型成員的偏移位置時(shí)則是將復(fù)合類型作為整體看待。
四 空struct/class和const,static成員
實(shí)例:

Code
struct empty{}; // 1
struct constAndStatic
{
const int i;
static char c;
const double d;
static void TestStatic(){}
void TestNoStatic(){}
}; // 16
布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

上面的實(shí)例中empty的大小為1,而constAndStatic的大小為16。
總結(jié):
因?yàn)閟tatic成員和函數(shù)其實(shí)是類層次的,不在對象中分配空間,而成員函數(shù)其實(shí)是被編譯為全局函數(shù)了,所以也不在對象中。
五 本節(jié)完,下次探討虛函數(shù)對內(nèi)存布局的影響!
六 單繼承與虛函數(shù)表
一 單繼承
1) 代碼:

Code
#include <iostream>
using namespace std;
class A
{
public:
void f1(){cout << "A::f1" << endl;}
void f2(){cout << "A::f2" << endl;}
virtual void v1(){cout << "A::v1" << endl;}
virtual void v2(){cout << "A::v2" << endl;}
int x;
};
class B : public A
{
public:
void f2(){cout << "B::f2" << endl;} // 覆蓋
void v2(){cout << "B::v2" << endl;} // 重寫
void f3(){cout << "B::f3" << endl;}
virtual void v3(){cout << "B::v3" << endl;}
int y;
};
class C : public B
{
public:
void f3(){cout << "C::f3" << endl;} // 覆蓋
void v1(){cout << "C::v1" << endl;} // 重寫
void v3(){cout << "C::v3" << endl;} // 重寫
int z;
};
2)類圖:

3)VS2008的編譯選項(xiàng)查看布局:

4)可視化表示:

5)代碼驗(yàn)證:

Code
typedef void (*Fun)();
void PrintVTable(A *pA)
{
int *pVT = (int*)*(int*)(pA);
Fun* pF = (Fun*)(pVT + 0);
int iLength = 0;
while(*pF != NULL)
{
(*pF)();
++iLength;
pF = (Fun*)(pVT + iLength);
}
}
void PrintMembers(A *pA)
{
int *p = (int*)(pA);
int i = 1;
while(i <= 3)
{
cout << *(p+i) << endl;
i++;
}
}
void TestVT()
{
A *pA = new C();
C *pC = dynamic_cast<C*>(pA);
pC->x = 10;
pC->y = 20;
pC->z = 30;
PrintVTable(pA);
PrintMembers(pA);
delete pA;
}
6)驗(yàn)證代碼運(yùn)行結(jié)果:

7)總結(jié):
單繼承的對象的布局,第一個(gè)為虛函數(shù)表指針vtbl,其后為成員且先基類后子類,虛函數(shù)表里包含了所有的虛函數(shù)的地址,以NULL結(jié)束。虛函數(shù)如果子類有重寫,就由子類的重新的代替。
二 單繼承運(yùn)行時(shí)類型轉(zhuǎn)化
1)代碼驗(yàn)證:

Code
void TestDynamicCast()
{
A *pA = new C();
cout << "A:" << pA << endl;
B *pB = dynamic_cast<B*>(pA);
cout << "B:" << pB << endl;
C *pC = dynamic_cast<C*>(pA);
cout << "C:" << pC << endl;
}
2)驗(yàn)證代碼運(yùn)行結(jié)果:

3)總結(jié):
我們上面看了單繼承的內(nèi)存布局,而這樣的內(nèi)存布局也就決定了當(dāng)dynamic_cast的時(shí)候,都還是同一地址,不需要做指針的移動。只是類型的改變即所能訪問的范圍的改變。
三 完!
七 多繼承與虛函數(shù)表
一 多重繼承
1) 代碼:

Code
#include <iostream>
using namespace std;
class B1
{
public:
int x;
virtual void v1(){ cout << "B1::v1" << endl; }
void f1(){cout << "B1::f1" << endl; }
};
class B2
{
public:
int y;
virtual void v2(){ cout << "B2::v2" << endl; }
void f2(){ cout << "B2::f2" << endl; }
};
class B3
{
public:
int z;
virtual void v3(){ cout << "B3::v3" << endl; }
void f3(){ cout << "B3::f3" << endl; }
};
class D : public B1, public B2, public B3
{
public:
int a;
void v3(){ cout << "D::v3" << endl; }
virtual void vD(){ cout << "D::vD" << endl; }
};
2)類圖:

3)VS2008的編譯選項(xiàng)查看布局:

4)可視化表示:

5)代碼驗(yàn)證:

Code
typedef void (*Fun)();
void PrintMember(int *pI)
{
cout << *pI << endl;
}
void PrintVT(int *pVT)
{
while(*pVT != NULL)
{
(*(Fun*)(pVT))();
pVT++;
}
}
void PrintVTAndMember(B1 *pD)
{
int *pRoot = (int*)pD;
int *pVTB1 = (int*)*(pRoot + 0);PrintVT(pVTB1);
int *pMB1 = pRoot +1; PrintMember(pMB1);
int *pVTB2 = (int*)*(pRoot + 2);PrintVT(pVTB2);
int *pMB2 = pRoot +3; PrintMember(pMB2);
int *pVTB3 = (int*)*(pRoot + 4);PrintVT(pVTB3);
int *pMB3 = pRoot +5; PrintMember(pMB3);
}
void TestVT()
{
B1 *pB1 = new D();
D *pD = dynamic_cast<D*>(pB1);
pD->x = 10;
pD->y = 20;
pD->z = 30;
pD->a = 40;
PrintVTAndMember(pD);
delete pD;
}
6) 驗(yàn)證代碼運(yùn)行結(jié)果:

7)總結(jié):
與單繼承相同的是所有的虛函數(shù)都包含在虛函數(shù)表中,所不同的多重繼承有多個(gè)虛函數(shù)表,當(dāng)子類對父類的虛函數(shù)有重寫時(shí),子類的函數(shù)覆蓋父類的函數(shù)在對應(yīng)的虛函數(shù)位置,當(dāng)子類有新的虛函數(shù)時(shí),這些虛函數(shù)被加在第一個(gè)虛函數(shù)表的后面。
二 多重繼承運(yùn)行時(shí)類型轉(zhuǎn)化
1)代碼驗(yàn)證:

Code
void TestDynamicCast()
{
B1 *pB1 = new D();
cout << "B1:" << pB1 << endl;
D *pD = dynamic_cast<D*>(pB1);
cout << "D:"<< pD << endl;
B2 *pB2 = dynamic_cast<B2*>(pB1);
cout << "B2:" << pB2 << endl;
B3 *pB3 = dynamic_cast<B3*>(pB1);
cout << "B3:" << pB3 << endl;
delete pD;
}
2)驗(yàn)證代碼的運(yùn)行結(jié)果:

3)總結(jié):
從多重繼承的內(nèi)存布局,我們可以看到子類新加入的虛函數(shù)被加到了第一個(gè)基類的虛函數(shù)表,所以當(dāng)dynamic_cast的時(shí)候,子類和第一個(gè)基類的地址相同,不需要移動指針,但是當(dāng)dynamic_cast到其他的父類的時(shí)候,需要做相應(yīng)的指針的移動。
三 完!
感謝,Thanks!
八 虛繼承與虛函數(shù)表
一 虛繼承
1) 代碼:

Code
#include <iostream>
using namespace std;
class B
{
public:
int i;
virtual void vB(){ cout << "B::vB" << endl; }
void fB(){ cout << "B::fB" << endl;}
};
class D1 : virtual public B
{
public:
int x;
virtual void vD1(){ cout << "D1::vD1" << endl; }
void fD1(){ cout << "D1::fD1" << endl;}
};
class D2 : virtual public B
{
public:
int y;
void vB(){ cout << "D2::vB" << endl;}
virtual void vD2(){ cout << "D2::vD2" << endl;}
void fD2(){ cout << "D2::fD2" << endl;}
};
class GD : public D1, public D2
{
public:
int a;
void vB(){ cout << "GD::vB" << endl;}
void vD1(){cout << "GD::vD1" << endl;}
virtual void vGD(){cout << "GD::vGD" << endl;}
void fGD(){cout << "GD::fGD" << endl;}
};
2)類圖:

3)VS2008的編譯選項(xiàng)查看布局:

4)可視化表示:

5)代碼驗(yàn)證:(此時(shí)的虛函數(shù)表不是以NULL結(jié)尾,為什么?)

Code
typedef void (*Fun)();
void PrintMember(int *pI)
{
cout << *pI << endl << endl;
}
void PrintVT(int *pVT)
{
while(*pVT != NULL)
{
(*(Fun*)(pVT))();
pVT++;
}
}
void PrintMemberAndVT(GD *pGD)
{
int *pRoot = (int*)pGD;
int *pD1VT = (int*)*(pRoot + 0);
(*(Fun*)(pD1VT))(); (*(Fun*)(pD1VT +1))();
int *pVB = (int*)*(pRoot +1); cout << "vbtable's adress:" << *pVB << endl;
int *pX = (pRoot + 2); PrintMember(pX);
int *pD2VT = (int*)*(pRoot + 3);
(*(Fun*)(pD2VT))();
int *pVB2 = (int*)*(pRoot +4); cout << "vbtable's adress:" << *pVB2 << endl;
int *pY = (pRoot + 5); PrintMember(pY);
int *pA = (pRoot + 6); PrintMember(pA);
int *pBVT = (int*)*(pRoot + 7);
(*(Fun*)(pBVT))();
int *pI = (pRoot + 8); PrintMember(pI);
}
void TestVT()
{
B *pB = new GD();
GD *pGD = dynamic_cast<GD*>(pB);
pGD->i = 10;
pGD->x = 20;
pGD->y = 30;
pGD->a = 40;
PrintMemberAndVT(pGD);
delete pGD;
}
6)驗(yàn)證代碼結(jié)果:

7)總結(jié):
虛繼承,使公共的基類在子類中只有一份,我們看到虛繼承在多重繼承的基礎(chǔ)上多了vbtable來存儲到公共基類的偏移。
二 虛繼承運(yùn)行時(shí)類型轉(zhuǎn)化
1)代碼驗(yàn)證:

Code
void TestDynamicCast()
{
B *pB = new GD();
GD *pGD = dynamic_cast<GD*>(pB);
cout << "GD:" << pGD << endl;
D1 *pD1 = dynamic_cast<D1*>(pB);
cout << "D1:" << pD1 << endl;
D2 *pD2 = dynamic_cast<D2*>(pB);
cout << "D2:" << pD2 << endl;
cout << "B:" << pB << endl;
}
2)驗(yàn)證代碼結(jié)果:

3)總結(jié):
還是從內(nèi)存布局來看dynamic_cast時(shí)地址的變化,第一個(gè)基類的地址與子類相同,其他的基類和虛基類需要做偏移。
三 完!
感謝,Thanks!
九 類型轉(zhuǎn)換
一 typeid與dynamic_cast
1)RTTI, Runtime Type Identification (RTTI) or Run-time type
information (RTTI),表示在運(yùn)行時(shí)動態(tài)決定變量的類型,來調(diào)用正確的虛函數(shù)。
RTTI在VS2008中默認(rèn)為關(guān)閉,可以通過修改編譯選項(xiàng)Enable Run-Time Type Info 為
Yes,來啟用RTTI,只有當(dāng)啟動RTTI時(shí),用來RTTI功能的typeid和dynamic_cast才能正常工作。
2)type_info,用來描述類型信息。type_info存儲了它所描述的類型的名字。RTTI就是使用type_info來實(shí)現(xiàn)的。type_info的定義如下:

Code
class type_info {
public:
virtual ~type_info();
bool operator== (const type_info& rhs) const;
bool operator!= (const type_info& rhs) const;
bool before (const type_info& rhs) const;
const char* name() const;
private:
type_info (const type_info& rhs);
type_info& operator= (const type_info& rhs);
};
問題:RTTI怎么實(shí)現(xiàn)那?對象,type_info,虛函數(shù)怎么關(guān)聯(lián)那?《深入C++對象模型》中說在虛函數(shù)表的開始存儲了類型信息,但是實(shí)際的VS2008中好像并沒有此信息,請高人指點(diǎn)哦!
3)typeid,在運(yùn)行時(shí)獲得對象的類型,typeid()返回的是const type_info&,而
type_info包含了對象真實(shí)類型的名字。typeid能被用來獲取一個(gè)引用對象或指針指向的對象的運(yùn)行時(shí)的真實(shí)類型。當(dāng)然如果對象為null或編譯
時(shí)沒有使用/GR的話,typeid的會拋出異常bad_typeid exception或__non_rtti_object。實(shí)例代碼:

Code
class Base
{
public:
virtual void f(){ }
};
class Derived : public Base
{
public:
void f2() {}
};
void main ()
{
Base *pB = new Derived();
const type_info& t = typeid(*pB);cout <<t.name() << endl;
delete pB;
Derived d;
Base& b = d;
cout << typeid(b).name() << endl;
}
運(yùn)行結(jié)果:

4)dynamic_cast,用來運(yùn)行時(shí)的類型轉(zhuǎn)化,需要/GR來正確運(yùn)行。
適用:
第一,用于所有的父子和兄弟間指針和引用的轉(zhuǎn)化,有類型安全檢查;
第二,對指針類型,如果不成功,返回NULL,對引用類型,如果不成功,則拋出異常;
第三,類型必須要有虛函數(shù),且打開/GR編譯選項(xiàng),否則不能使用dynamic_cast。
實(shí)例代碼:

Code
class AA
{
public:
virtual void do_sth(){ std::cout<<"AA"n"; }
};
class BB
{
public:
virtual void do_sth(){ std::cout<<"BB"n"; }
};
class CC : public AA, public BB
{
public:
virtual void do_sth(){ std::cout<<"CC"n"; }
};
void DynamicCastTest()
{
AA *pA = new CC;
BB *pB = dynamic_cast<BB*>(pA);
if(pB != NULL)
cout << "cast successful!" << endl;
CC *pC = dynamic_cast<CC*>(pA);
if(pC != NULL)
cout << "cast successful!" << endl;
}
二 其他cast
1)隱式轉(zhuǎn)化,不需要任何操作符,轉(zhuǎn)化被自動執(zhí)行,當(dāng)一個(gè)值被賦值到它所兼容的類型時(shí)。
適用:
第一,內(nèi)置基本類型的兼容轉(zhuǎn)化;
第二, 子類指針,引用向父類的轉(zhuǎn)化;
實(shí)例:

Code
class A
{
public:
virtual ~A(){}
};
class B : public A
{
};
void ImplicitCast()
{
short a = 2000;
int b;
b = a;
double d = 10.05;
int i;
i = d;
int j = 75;
char c;
c = j;
A* pA = new B();
}
2)強(qiáng)制類型轉(zhuǎn)化,即我們常說的C風(fēng)格的類型轉(zhuǎn)化,基本上可以用于所有的轉(zhuǎn)化,但是沒有意義的轉(zhuǎn)化除外,但是父子類,兄弟間的轉(zhuǎn)化沒有類型檢查可能導(dǎo)致運(yùn)行是錯(cuò)誤。
適用:
第一,基本類型轉(zhuǎn)化;
第二,void*到其他指針的轉(zhuǎn)化;
第三,去除const;
第五,函數(shù)指針的轉(zhuǎn)化;
第六,父子類轉(zhuǎn)化,但是多重繼承和兄弟轉(zhuǎn)化,可能有運(yùn)行時(shí)錯(cuò)誤,沒有類型檢查;
第七,任何兩個(gè)類,但是沒有實(shí)際意義,運(yùn)行可能出錯(cuò);
第八,不能用于沒有意義的轉(zhuǎn)化,嚴(yán)厲禁止,例如,你不能用static_cast象用C風(fēng)格的類型轉(zhuǎn)換一樣把struct轉(zhuǎn)換成int類型,或者把double類型轉(zhuǎn)換成指針類型;
第九,在C++一般更推薦新加的static_cast,const_cast,dynamic_cast和reinterpret_cast轉(zhuǎn)化方式;
實(shí)例:

Code
class CDummy
{
public:
CDummy(float x, float y)
{
i = x;
j = y;
}
private:
float i,j;
};
class CAddition
{
public:
CAddition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
private:
int x,y;
};
int Testing()
{
std::cout << "Testing" << std::endl;
return 10;
}
void ExplicitCast()
{
double r = (double)1 / 3;
int *pi = new int(10);
void *pV;
pV = pi;
int *pj = (int*)pV; // 或 int *pj = int*(pV);
const int* pa = new int(20);
int *pb;
pb = (int*)pa;
*pb = 30;
std::cout << *pa << std::endl;
typedef void (*Fun)();
Fun f = (Fun)Testing;
f();
// 多重繼承或?qū)⑿值荛g的轉(zhuǎn)化可能會出錯(cuò)
// 雖然可以正確的編譯,但是運(yùn)行有問題,所以我們不做沒有意義的轉(zhuǎn)化
//CDummy d(10,30);
//CAddition * padd;
//padd = (CAddition*) &d;
//std::cout << padd->result();
// 不做沒有意義的轉(zhuǎn)化
//// error
//struct st{int i; double d;};
//st s;
//int x = (int)s; //c2440
//double y = 10.0;
//int *p = (int*)y; // c2440
}
3)static_cast在功能上基本上與C風(fēng)格的類型轉(zhuǎn)換一樣強(qiáng)大,含義也一樣。
它也有功能上限制:
第一,不能兄弟間轉(zhuǎn)化,父子間轉(zhuǎn)化沒有類型安全檢查,有可能會導(dǎo)致運(yùn)行時(shí)錯(cuò)誤,父子兄弟的動態(tài)轉(zhuǎn)化應(yīng)該適用dynamic_cast;
第二,不能去除const,適用專用的const_cast;
第三,不能用于兩個(gè)沒有繼承關(guān)系的類,當(dāng)然實(shí)際上這樣的轉(zhuǎn)化也是沒有意義的;
第四,當(dāng)然也不支持沒有意義的轉(zhuǎn)化,例如,你不能用static_cast象用C風(fēng)格的類型轉(zhuǎn)換一樣把struct轉(zhuǎn)換成int類型,或者把double類型轉(zhuǎn)換成指針類型;
4)const_cast,用來修改類型的const或volatile屬性。
適用:
第一,常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對象;
第二,常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對象;
第三,常量對象被轉(zhuǎn)換成非常量對象;
實(shí)例:

Code
void ConstCastTest()
{
const int* pa = new int(20);
int *pb;
pb = const_cast<int*>(pa);
*pb = 30;
std::cout << *pa << std::endl;
}
5)reinterpret_cast,此轉(zhuǎn)型操作符的結(jié)果取決于編譯器,用于修改操作數(shù)類型,非類型安全的轉(zhuǎn)換符。
適用:
一般不推薦使用,但是一般用來對函數(shù)指針的轉(zhuǎn)化。
實(shí)例:

Code
// 不可以移植,不推薦使用
int ReinterpretTest()
{
struct dat { short a; short b;};
long value = 0x00100020;
dat * pd = reinterpret_cast<dat *> (&value);
std::cout << pd->a << std::endl; // 0x0020
std::cout << pd->b << std::endl; // 0x0010
return 0;
}
typedef void (*Fun)();
int Testing()
{
std::cout << "Testing" << std::endl;
return 10;
}
void ReinterpretTest2()
{
//Fun f = (Fun)Testing;
//f();
Fun f = reinterpret_cast<Fun>(Testing);
f();
}
三 總結(jié)
在C++一般更推薦新加的static_cast,const_cast,dynamic_cast和reinterpret_cast轉(zhuǎn)化方式;