免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
inside the C Object model總結(jié)

一. 關(guān)于對(duì)象

1.內(nèi)聯(lián)函數(shù):能夠除去函數(shù)調(diào)用的開支,每一處內(nèi)聯(lián)函數(shù)的調(diào)用都是代碼的復(fù)制。這是一種空間換取時(shí)間的做法,若函數(shù)代碼量大或者有循環(huán)的情況下,不宜內(nèi)聯(lián)(這件事有些編譯器會(huì)自動(dòng)幫你做)。在類中若直接將函數(shù)實(shí)現(xiàn)寫在類體內(nèi),默認(rèn)內(nèi)聯(lián)。如果函數(shù)因其復(fù)雜度或構(gòu)建等問題被判斷為不能成為inline函數(shù),它將被轉(zhuǎn)化為一個(gè)static函數(shù)。如果一個(gè)inline函數(shù)被調(diào)用太多次的話,會(huì)產(chǎn)生大量的擴(kuò)展碼,使程序大小暴漲。

2.C++對(duì)象模型:

3.組合,而非繼承是把C和C++結(jié)合的唯一可行方法。

4.C++程序的三種程序設(shè)計(jì)范式:

(1)procedural model,即C語言的模式

(2)ADT model,即所謂的封裝,將屬性與方法封裝在一起

(3)object-oriented model,支持繼承、多態(tài)(只有通過指針或引用來使用,往往只有到執(zhí)行期才知道指向哪種object)。

5.class object所占內(nèi)存計(jì)算:

nonstatic data member總和+aligment填補(bǔ)(32位機(jī)器為4)+支持虛擬機(jī)制產(chǎn)生的額外內(nèi)存(如果有的話)

這里需要說明一下支持虛擬機(jī)制產(chǎn)生的額外內(nèi)存,一般含有虛函數(shù)的類會(huì)產(chǎn)生一個(gè)指向虛擬表的指針(vptr),所以會(huì)增加4byte的內(nèi)存。對(duì)于虛擬繼承來講,所有父類的virtual table指針全部被保留。舉例說明:

1 class point
2 {
3 public:
4     virtual void show(){};
5 protected:
6     int _x,_y;
7 };

sizeof結(jié)果為:12

1 class point3d : public  point
2 {
3  protected:
4     int _z;
5 };

sizeof結(jié)果為:16

 1 class point3d : virtual public  point
 2 {
 3 protected:
 4     int _z;
 5 };
 6 
 7 class vertex : virtual public  point
 8 {
 9 protected:
10     vertex* next;
11 };

這兩個(gè)類的size均為20

1 class vertex3d : public  point3d,public vertex
2 {
3 protected:
4     int a;
5 };

sizeof結(jié)果為32,包括5個(gè)4byte的變量,和三個(gè)父類虛函數(shù)指針。

 二.構(gòu)造函數(shù)語意學(xué)

1.在編譯需要的時(shí)候,編譯器為自動(dòng)生成default constructor,若類已有constructor,編譯器會(huì)將必要的代碼安插進(jìn)去(安插在user code之前)。有些類沒有聲明constructor,會(huì)有一個(gè)default constructor被隱式聲明出來(所以在用戶申明了構(gòu)造函數(shù)的情況下,并不會(huì)有default 構(gòu)造函數(shù)被合成),但聲明出來的將是一個(gè)trivial constructor(并不會(huì)被編譯器合成),除非是在必要的情況下,一個(gè)nontrivial constructor才會(huì)被編譯器合成,包括以下幾種情況:

(1)帶有Default constructor的Member Class Object。即某一個(gè)成員含有構(gòu)造函數(shù),假設(shè)類A的對(duì)象是類B的成員變量,若B包含有多個(gè)構(gòu)造函數(shù),均沒有調(diào)用A的構(gòu)造函數(shù),則B的每個(gè)構(gòu)造函數(shù)將由編譯器安插調(diào)用A的構(gòu)造函數(shù)的代碼。

(2)帶有Default constructor的base Class。與上面類似,但需要注意:調(diào)用base class的constructor優(yōu)先于member class的constructor。

(3)帶有virtual function的class。constructor要負(fù)責(zé)生成并初始化指向虛函數(shù)表的指針vptr。

(4)帶有virtual base class 的class。不同的編譯器對(duì)虛基類的處理不同,但是總是會(huì)產(chǎn)生一個(gè)指針去實(shí)現(xiàn)虛擬機(jī)制,MSVC將虛函數(shù)表與虛基類表合并,均使用指針vpt尋址。

2.在用戶沒有定義拷貝構(gòu)造函數(shù)的情況下,默認(rèn)采用memberwise初始化,即逐成員初始化,并進(jìn)行位逐次拷貝。但在一些情況下,一個(gè)nontrivial的拷貝構(gòu)造函數(shù)將被合成:

(1)類的member class object聲明了一個(gè)copy constructor

(2)類的base class 聲明了一個(gè)copy constructor

(3)類聲明了虛函數(shù)

(4)類的繼承串鏈中包含虛擬繼承。

以(3)舉例,A中聲明了一個(gè)虛函數(shù),B是A的子類,現(xiàn)進(jìn)行如下操作B b;A a=b;此時(shí)A會(huì)合成一個(gè)nontrivial copy constructor顯示設(shè)定a的vptr指向類A的虛函數(shù)表,而不是直接把B的虛函數(shù)表指針拷貝過來。

如果您聲明的類的成員變量包含指針,請(qǐng)務(wù)必聲明一個(gè)copy constructor?。。?!

3.關(guān)于程序的轉(zhuǎn)化:

(1)顯式的copy初始化操作會(huì)轉(zhuǎn)化為兩階段,如:X x1(x0)會(huì)被編譯器轉(zhuǎn)化為兩個(gè)階段 1.定義,即占內(nèi)存:X x1 2.調(diào)用拷貝構(gòu)造函數(shù)x1.X::X(x0)

(2)函數(shù)參數(shù)的初始化,一種最常用的策略便是會(huì)形成一個(gè)臨時(shí)變量來存儲(chǔ)傳進(jìn)來的參數(shù),函數(shù)結(jié)束后被銷毀。如果函數(shù)的形參是引用或指針,則不會(huì)這樣咯。

(3)函數(shù)返回值的初始化,還是會(huì)形成一個(gè)臨時(shí)變量,例如:函數(shù)X foo(){X xx;....; return xx }會(huì)被轉(zhuǎn)化為:void foo(X & _result){X xx;....;_result.X::X(xx);return;},因此在程序員寫下X xx=foo();將被轉(zhuǎn)化為:X xx; foo(xx); 這就是所謂的具名優(yōu)化(NRV優(yōu)化),如果你的聲明包含copy constructor,就很可能被具名優(yōu)化,當(dāng)然只是很可能,到底優(yōu)化沒有還得看編譯器,因?yàn)槟愀静恢谰幾g器會(huì)干些什么??!

4.必須使用成員初始化隊(duì)伍初始化的情況:

(1)初始化一個(gè)reference member時(shí)。

(2)初始化一個(gè)const member時(shí)

(3)調(diào)用一個(gè)base class的constructor時(shí)

(4)調(diào)用一個(gè)member class的constructor時(shí)

需要說明的是:成員初始化隊(duì)伍的初始化方法比在構(gòu)造函數(shù)里面復(fù)制效率要高一點(diǎn),因此應(yīng)該多用哦!舉例說明:

class man{public: man(){_name =0;_age=0;};priavte: string _name;int _age;}其構(gòu)造函數(shù)中的賦值由于=的存在,必須產(chǎn)生一個(gè)臨時(shí)變量,此時(shí)構(gòu)造函數(shù)會(huì)被轉(zhuǎn)化為:man(){_name.string::string();//占內(nèi)存咯   string temp=String(0); _name.string::operator=(temp); temp.string::~string(); _age=0; } 臨時(shí)變量的產(chǎn)生與析構(gòu)將拉低效率。而若以初始化隊(duì)伍初始化,即man():_name(0),_age(0){}將被轉(zhuǎn)化為:man(){_name.string::string(0);_age=0; }哪個(gè)快顯而易見吧?經(jīng)自己實(shí)驗(yàn)測(cè)試確實(shí)是快了一點(diǎn),但是仍然在一個(gè)數(shù)量級(jí)

但是這里有一點(diǎn)需要注意,在成員初始化隊(duì)伍中。初始化的順序并不是按照這個(gè)list的順序,而是按照類里面成員聲明的順序,例如:class x{int i;int j; X (int val):j(val),i(j){};},這段代碼會(huì)出現(xiàn)異常,因?yàn)閕會(huì)比j先初始化,而此時(shí)j還沒有被初始化,所以i(j)肯定會(huì)異常咯。但是如果這樣就對(duì)了:class x{int i;int j; X (int val):j(val){i=j};},因?yàn)榫幾g器會(huì)把初始化隊(duì)伍的代碼放在explicit user code之前。

對(duì)象在使用前初始化是個(gè)好習(xí)慣,尤其是在對(duì)象有大量成員變量的情況下。

  三.Data語意學(xué)

1.一個(gè)空類的聲明將占1個(gè)字節(jié)的內(nèi)存,使得整個(gè)class的不同object得以在內(nèi)存中配置獨(dú)一無二的地址。但是當(dāng)你在這個(gè)空類中聲明一個(gè)非靜態(tài)成員變量時(shí)(比如int _a),不同的編譯器會(huì)產(chǎn)生不同的效果,一般情況下類會(huì)變成4字節(jié),因?yàn)樵瓉淼哪?字節(jié)已經(jīng)不需要了,但是有的編譯器會(huì)保留那1字節(jié),這樣有用的字節(jié)數(shù)就為5字節(jié),再加上32機(jī)器的補(bǔ)全(alignment機(jī)制),這個(gè)類將有8個(gè)字節(jié)。

2.將類的函數(shù)放在類體外中定義,對(duì)函數(shù)本體的分析會(huì)在整個(gè)class聲明都出現(xiàn)后才開始,因此這是一個(gè)良好的習(xí)慣咯。

3.關(guān)于Data member的布局:

(1)Nonstatic data member在class object中的順序?qū)⒑捅宦暶鞯臄?shù)據(jù)順序一樣。即同一access section(如:private、protected等)較晚出現(xiàn)的member在class object中有較高的地址,但是各個(gè)members之間并不一定是連續(xù)排列的,因?yàn)閙embers的邊界調(diào)整(alignment)可能就需要填補(bǔ)一些byte。舉個(gè)栗子:class test{char _a;int _b; char _c;}   sizeof的結(jié)果是12,沒錯(cuò)確實(shí)不是8,是12!?。。。?!

(2)C++標(biāo)準(zhǔn)雖然也允許多個(gè)access section之中的data members自由排列,但目前各家編譯器都是將一個(gè)以上的access sections連鎖在一起,依照聲明的順序成為一個(gè)連續(xù)的區(qū)塊,因?yàn)檫@樣畢竟效率高嘛。

(3)編譯器產(chǎn)生的vptr將被允許安插在對(duì)象的任何位置,但是大部分編譯器還是將其安插在所有顯式聲明的成員變量之后,但也有放最前面的。

4.關(guān)于data member的存取:

(1)對(duì)于靜態(tài)成員變量,通過對(duì)象的指針和對(duì)象存取是完全相同的。因?yàn)閟tatic成員比昂兩并不存在類內(nèi),而是放在程序的data segment。如果出現(xiàn)兩個(gè)不同的類聲明了相同名字的static member,那它們都被放在data segment中會(huì)導(dǎo)致命名沖突,編譯器的解決方法是對(duì)每一個(gè)static member進(jìn)行name-mangling,使之名字唯一。(name-mangling還會(huì)在函數(shù)重載中用到)

(2)對(duì)于非靜態(tài)成員函數(shù),其直接存取效率和存取一個(gè)C struct一樣。但是需要注意如果某個(gè)類繼承自抽象類(包含虛函數(shù)),那么如果用指針存取就會(huì)降低效率,因?yàn)橐恢钡綀?zhí)行其才能知道父類的指針到底指向的是子類還是父類,因此這種間接性會(huì)降低效率。同理虛擬繼承時(shí),當(dāng)子類要存取父類的成員變量時(shí),由于間接性的原因通過對(duì)象或指針存取都將降低效率。

5.繼承條件下的data member分布:

(1)單一繼承:很簡(jiǎn)單

(2)關(guān)于繼承鏈產(chǎn)生alignment的情況:

對(duì)于以下三個(gè)類:class concrete1{int val;char bit1}; class concrete2:public concrete1 {char bit2}; class concrete3:public concrete2 {char bit3} ;

concrete1所占內(nèi)存為8,concrete2為12,concrete3為16. 

(3)抽象類(包含虛函數(shù))作為父類的單一繼承內(nèi)存分布:

 

 (4)抽象類(包含虛函數(shù))作為父類的多重繼承內(nèi)存分布,如:

 

 其內(nèi)存分布應(yīng)如下:兩個(gè)虛表指針都保存。

 

 (5)虛擬繼承下的內(nèi)存分布:

 

其內(nèi)存布局如下有2種策略:

 a.每個(gè)類除了虛表指針外,再添加一個(gè)虛基類指針,以指向自己的虛基類,如:(cfront)

 

 b.擴(kuò)展虛函數(shù)表,也將虛基類的指針存進(jìn)來。一般的存取策略是:若offset為正存取的是虛函數(shù)地址,為負(fù)存取的是虛基類地址:

四.Function語意學(xué)

1.關(guān)于不同成員函數(shù)的調(diào)用方式:

(1)非靜態(tài)成員函數(shù),編譯器內(nèi)部會(huì)將成員函數(shù)實(shí)例轉(zhuǎn)換為對(duì)等的nonmember函數(shù)實(shí)例。其過程如下:

a.改寫函數(shù)原型,提供額外的參數(shù)(即指向?qū)ο蟮膖his指針),如類point的方法 point point::foo();會(huì)被轉(zhuǎn)化為point point::foo(point * const this);

b.若函數(shù)體內(nèi)有對(duì)對(duì)象成員變量的操作,全部替換為帶this指針的操作。如函數(shù)體內(nèi)若將兩個(gè)成員變量相加,_x+_y會(huì)被轉(zhuǎn)化為this->_x+this->_y;

c.對(duì)程序進(jìn)行name-mangling處理(前面三.4.(1)頁提到過),使函數(shù)成為整個(gè)程序中唯一的詞匯,如:foo_6pointFv(point * const this);注意:這里可以想到重載的函數(shù)經(jīng)過mangling之后名字就不一樣了,所以調(diào)用起來沒問題。

而對(duì)象對(duì)此函數(shù)的調(diào)用會(huì)由obj.foo()轉(zhuǎn)化為foo_6pointFv(&obj)

(2)虛擬成員函數(shù)通過虛擬表存取。這就是C++多態(tài)的實(shí)現(xiàn)途徑,以一個(gè)public base class指針尋址出一個(gè)derived class object。

a.單一繼承下virtual function的布局:

當(dāng)某個(gè)父類的指針調(diào)用虛函數(shù)時(shí),雖然我們并不知道父類指針指向的究竟是什么(可能是父類對(duì)象也可能是子類對(duì)象),但通過vptr可以去到該對(duì)象的virtual table,而每個(gè)函數(shù)在virtual table中的順序是固定的,恩,多態(tài)就是這么實(shí)現(xiàn)的。

b.多重繼承下的virual table布局:

于是,當(dāng)你將一個(gè)子類的地址賦予一個(gè)Base1類的指針或子類指針,被處理的virtual table是圖中的第一個(gè)表格,當(dāng)你講一個(gè)子類賦予一個(gè)Base2類的指針時(shí),被處理的virtual table是圖中第二個(gè)表格。

有三種情況下第二個(gè)基類會(huì)影響對(duì)virtual function的支持:

  • 指向第二個(gè)基類的指針調(diào)用子類的函數(shù):如Base2 *ptr = new Derived();delete ptr;后面這一句要調(diào)用虛析構(gòu)函數(shù),因此ptr需移動(dòng)sizeof(Base1)個(gè)byte。(即從base2 subobject開頭處移動(dòng)到derived對(duì)象的開頭處)
  • 指向子類的指針調(diào)用第二個(gè)base class中繼承而來的虛函數(shù):如 Derived *pder=new Derived(); pder->mumble();從圖上看到mumble()函數(shù)是第二個(gè)基類的虛函數(shù),為了能調(diào)用它,需將pder移動(dòng)sizeof(Base1)個(gè)byte。(即從derived對(duì)象的開頭處移動(dòng)到base2 subobject開頭處)
  • 函數(shù)的返回值如果是Base2指針:如Base2 *pb1= new Derived; Base2 * pb2=pb1->clone(); pb1->clone()會(huì)傳回一個(gè)指向子類對(duì)象起始位置的指針,該對(duì)象地址在賦予pb2之前會(huì)經(jīng)過調(diào)整以指向base2指針。

(3)static成員函數(shù)具有以下特性:

a.不能直接存取其class類的非靜態(tài)成員變量。

b.不能被聲明為const、volatile或virtual

c.不需非得經(jīng)由class obj調(diào)用。

五.進(jìn)一步深入構(gòu)造、析構(gòu)、拷貝語意學(xué)

 1.類的對(duì)象是可以經(jīng)由explicit initialization list初始化的,如class point {point(){};public:float _x,_y,_z;},point local = {1.0,1.0,1.0}; 雖然這樣效率高一點(diǎn),但是這樣做有條件:只有在class member是常量的情況下奏效;list里面只能是常量;初始化失敗可能性很高呢。如果在某些程序中,可能需要將大量的常量數(shù)據(jù)傾倒給程序,可以考慮此法。

2.constructor的執(zhí)行算法通常如下:

(1)所有virtual base class和上一層base class的constructor被調(diào)用

(2)對(duì)象的vptr(s)初始化,指向相關(guān)類的虛表。

(3)如果有member initialization list的話,將他們?cè)赾onstructor體內(nèi)擴(kuò)展開。

(4)最后調(diào)用程序員自己的代碼。

3.不準(zhǔn)將一個(gè)class object復(fù)制給另一個(gè)class object的方法:將copy assignment operator(即=)設(shè)為private。

4.如果類沒有定義析構(gòu)函數(shù),那么只有在類內(nèi)含member object或類的父類擁有析構(gòu)函數(shù)的情況下編譯器才會(huì)自動(dòng)合成一個(gè)來,否則析構(gòu)函數(shù)被視為不需要,也就不用被合成和調(diào)用。

5.C++之父強(qiáng)調(diào):“你應(yīng)該拒絕那種被我陳偉’對(duì)稱策略'的奇怪想法:你已經(jīng)定義了一個(gè)constructor,所以你以為提供一個(gè)destructor也是天經(jīng)地義的事情,事實(shí)上,你應(yīng)該根據(jù)需要而非感覺定義析構(gòu)函數(shù)!”。

6.在C++程序設(shè)計(jì)中,將所有的object聲明放在函數(shù)或某個(gè)區(qū)段的起始處完全是個(gè)陋習(xí)!因?yàn)槭紫瘸霈F(xiàn)在函數(shù)起始處的應(yīng)該是各種各樣的檢查,檢查如果不符合就會(huì)跳出函數(shù),那你聲明的變量不是白聲明了。

六.執(zhí)行期語意學(xué)

1.全局對(duì)象,C++所有的全局對(duì)象都被放置在程序的data segment中,如果顯式地給了它一個(gè)值,這便是全局變量的初值,否則設(shè)初值為0。C++保證一定會(huì)在main()函數(shù)第一次使用全局變量前將它構(gòu)造出來。

2.動(dòng)態(tài)初始化與靜態(tài)初始化:一般而言,局部變量是在程序運(yùn)行到某處后再棧中申請(qǐng)分配地址,這就是動(dòng)態(tài)初始化。而全局變量或靜態(tài)變量在程序開始的時(shí)候就分配好了地址,可以讓程序放心使用,這就叫靜態(tài)初始化。

3.new運(yùn)算符是以標(biāo)準(zhǔn)的malloc()完成的,delete運(yùn)算總是以標(biāo)準(zhǔn)的C free()完成的。

4.如果你new了一個(gè)對(duì)象數(shù)組,如point * ptr=new point[10];則刪除必須要delete [] ptr;如果delete ptr;那將只有一個(gè)元素被析構(gòu)。

5.T c=a+b;總是比 c=a+b有效率一些,因?yàn)楹笳呖倳?huì)產(chǎn)生臨時(shí)變量來存放a+b的結(jié)果。而編譯器廠商一般都會(huì)實(shí)現(xiàn)T opratior+ (const T&,const T&),這樣就不會(huì)產(chǎn)生臨時(shí)對(duì)象。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
C++純虛函數(shù)調(diào)用
ES6的Class
Effective C++讀書筆記
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服