萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> 編程語言綜合 >> C++學習從零開始(六)

C++學習從零開始(六)

  再看main函數,先通過ABC a;定義了一個變量,因為要在棧上分配一塊內存,即創建了一個數字(創建裝數字的內存也就導致創建了數字,因為內存不能不裝數字),進而創建了一個ABC的實例,進而調用ABC的構造函數。由於這裡沒有給出參數(後面說明),

  因此調用了ABC::ABC(),進而a.a為1,a.pF和a.count都為0。接著定義了變量r,但由於它是ABC&,所以並沒有在棧上分配內存,進而沒有創建實例而沒有調用ABC::ABC。接著調用a.Do,分配了一塊內存並把首地址放在a.pF中。

  注意上面變量b的定義,其使用了之前提到的函數式初始化方式。它通過函數調用的格式調用了ABC的構造函數ABC::ABC( long, long )以初始化ABC的實例b。因此b.a為10,b.count為30,b.pF為一內存塊的首地址。但要注意這種初始化方式和之前提到的“{}”方式的不同,前者是進行了一次函數調用來初始化,而後者是編譯器來初始化(通過生成必要的代碼)。由於不調用函數,所以速度要稍快些(關於函數的開銷在《C++從零開始(十五)》中說明)。還應注意不能ABC b = { 1, 0, 0 };,因為結構ABC已經定義了兩個構造函數,則它只能使用函數式初始化方式初始化了,不能再通過“{}”方式初始化了。上面的b在一對大括號內,回想前面提過的變量的作用域,因此當程序運行到ABC *p = new ABC[10];時,變量b已經消失了(超出了其作用域),即其所分配的內存語法上已經釋放了(實際由於是在棧上,其並沒有被釋放),進而調用ABC的析構函數,將b在ABC::ABC( long, long )中分配的內存釋放掉以實現掃尾功能。

  對於通過new在堆上分配的內存,由於是new ABC[10],因此將創建10個ABC的實例,進而為每一個實例調用一次ABC::ABC(),注意這裡無法調用ABC::ABC( long, long ),因為new操作符一次性就分配了10個實例所需要的內存空間,C++並沒有提供語法(比如使用“{}”)來實現對一次性分配的10個實例進行初始化。接著調用了delete[] p;,這釋放剛分配的內存,即銷毀了10個實例,因此將調用ABC的析構函數10次以進行10次掃尾工作。

  注意上面聲明了全局變量g_ABC,由於是聲明,並不是定義,沒有分配內存,因此未產生實例,故不調用ABC的構造函數,而g_a由於是全局變量,C++保證全局變量的構造函數在開始執行main函數之前就調用,所有全局變量的析構函數在執行完main函數之後才調用(這一點是編譯器來實現的,在《C++從零開始(十九)》中將進一步討論)。因此g_a.ABC( 10, 34 )的調用是在a.ABC()之前,即使它的位置在a的定義語句的後面。而全局變量g_p的初始化的數字是通過new操作符的計算得來,結果將在堆上分配內存,進而生成5個ABC實例而調用了ABC::ABC()5次,由於是在初始化g_p的時候進行分配的,因此這5次調用也在a.ABC()之前。由於g_p僅僅只是記錄首地址,而要釋放這5個實例就必須調用delete(不一定,也可不調用delete依舊釋放new返回的內存,在《C++從零開始(十九)》中說明),但上面並沒有調用,因此直到程序結束都將不會調用那5個實例的析構函數,那將怎樣?後面說明異常時再討論所謂的內存洩露問題。

  因此構造的意思就是剛分配了一塊內存,還未初始化,則這塊內存被稱作原始數據(Raw Data),前面說過數字都必須映射成算法中的資源,則就存在數字的有效性。比如映射人的年齡,則這個數字就不能是負數,因為沒有意義。所以當得到原始數據後,就應該先通過構造函數的調用以保證相應實例具有正確的意義。而析構函數就表示進行掃尾工作,就像上面,在某實例運作的期間(即操作此實例的代碼被執行的時期)動態分配了一些內存,則應確保其被正確釋放。再或者這個實例和其他實例有關系,因確保解除關系

  (因為這個實例即將被銷毀),如鏈表的某個結點用類映射,則這個結點被刪除時應在其析構函數中解除它與其它結點的關系。

  派生和繼承

  上面我們定義了類Radiogram來映射收音機,如果又需要映射數字式收音機,它和收音機一樣,即收音機具有的東西它都具有,不過多了自動搜台、存儲台、選台和刪除台的功能。這裡提出了一個類型體系,即一個實例如果是數字式收音機,那它一定也是收音機,即是收音機的一個實例。比如蘋果和梨都是水果,則蘋果和梨的實例一定也是水果的實例。這裡提出三個類型:水果、蘋果和梨。其中稱水果是蘋果的父類(父類型),蘋果是水果的子類(子類型)。同樣,水果也是梨的父類,梨是水果的子類。這種類型體系是很有意義的,因為人類就是用這種方式來認知世界的,它非常符合人類的思考習慣,因此C++又提出了一種特殊語法來對這種語義提供支持。

  在定義自定義類型時,在類型名的後面接一“:”,然後接public或protected或private,接著再寫父類的類型名,最後就是類型定義符“{}”及相關書寫。

  如下:

  class DigitalRadiogram : public Radiogram

  {

  protected: double m_Stations[10];

  public: void SearchStation(); void SaveStation( unsigned long );

  void SelectStation( unsigned long ); void EraseStation( unsigned long );

  };

  上面就將Radiogram定義為了DigitalRadiogram的父類,DigitalRadiogram定義成了Radiogram的子類,被稱作類Radiogram派生了類DigitalRadiogram,類DigitalRadiogram繼承了類Radiogram。

  上面生成了5個映射元素,就是上面的4個成員函數和1個成員變量,但實際不止。由於是從Radiogram派生,因此還將生成7個映射,就是類Radiogram的7個成員,但名字變化了,全變成DigitalRadiogram::修飾,而不是原來的Radiogram::修飾,但是類型卻不變化。比如其中一個映射元素的名字就為DigitalRadiogram::m_bPowerOn,類型為bool Radiogram::,映射的偏移值沒變,依舊為16。同樣也有映射元素DigitalRadiogram::TurnFreq,類型為void ( Radiogram:: )( double ),映射的地址依舊沒變,為Radiogram::TurnFreq所對應的地址。因此就可以如下:

  void DigitalRadiogram::SaveStation( unsigned long index )

  {

  if( index >= 10 ) return;

  m_Station[ index ] = m_Frequency; m_bPowerOn = true;

  }

  DigitalRadiogram a; a.TurnFreq( 10 ); a.SaveStation( 3 );

  上面雖然沒有聲明DigitalRadiogram::TurnFreq,但依舊可以調用它,因為它是從Radiogram派生來的。注意由於a.TurnFreq( 10 );沒有書寫全名,因此實際是a.DigitalRadiogram::TurnFreq( 10 );,因為成員操作符左邊的數字類型是DigitalRadiogram。如果DigitalRadiogram不從Radiogram派生,則不會生成上面說的7個映射,結果a.TurnFreq( 10 );將錯誤。

  注意上面的SaveStation中,直接書寫了m_Frequency,其等同於this->m_Frequency,由於this是

  DigitalRadiogram*(因為在DigitalRadiogram::SaveStation的函數體內),所以實際為this->DigitalRadiogram::m_Frequency,也因此,如果不是派生自Radiogram,則上面將報錯。並且由類型匹配,很容易知道:void ( Radiogram::*p )( double ) = DigitalRadiogram::TurnFreq;。雖然這裡是DigitalRadiogram::TurnFreq,但它的類型是void ( Radiogram:: )( double )。

  應注意在SaveStation中使用了m_bPowerOn,這個在Radiogram中被定義成私有成員,也 上面通過派生而生成的7個映射元素各自的權限是什麼?先看上面的派生代碼:

  class DigitalRadiogram : public Radiogram {…};

  這裡由於使用public,被稱作DigitalRadiogram從Radiogram公共繼承,如果改成protected則稱作保護繼承,如果是private就是私有繼承。有什麼區別?通過公共繼承而生成的映射元素(指從Radiogram派生而生成的7個映射元素),各自的權限屬性不變化,即上面的DigitalRadiogram::m_Frequency對類DigitalRadiogram來說依舊是protected,而DigitalRadiogram::m_bPowerOn也依舊是private。保護繼承則所有的公共成員均變成保護成員,其它不變。即如果保護繼承,DigitalRadiogram::TurnFreq對於DigitalRadiogram來說將為protected。私有繼承則將所有的父類成員均變成對於子類來說是private。因此上面如果私有繼承,則DigitalRadiogram::TurnFreq對於DigitalRadiogram來說是private的。

  上面可以看得很簡單,即不管是什麼繼承,其指定了一個權限,父類中凡是高於這個權限的映射元素,都要將各自的權限降低到這個權限(注意是對子類來說),然後再繼承給子類。上面一直強調“對於子類來說”,什麼意思?如下:

  struct A { long a; protected: long b; private: long c; };

  struct B : protected A { void AB(); };

  struct C : private B { void ABC(); };

  void B::AB() { b = 10; c = 10; }

  void C::ABC() { a = 10; b = 10; c = 10; AB(); }

  A a; B b; C c; a.a = 10; b.a = 10; b.AB(); c.AB();

  上面的B的定義等同於struct B { protected: long a, b; private: long c; public: void AB

  (); };。

  上面的C的定義等同於struct C { private: long a, b, c; void AB(); public: void ABC

  (); };

  因此,B::AB中的b = 10;沒有問題,但c = 10;有問題, 因為編譯器看出B::c是從父類繼承生成的,而它對於父類來說是私有成員,因此子類無權訪問,錯誤。接著看C::ABC,a = 10;和b = 10;都沒問題,因為它們對於B來說都是保護成員,但c = 10;將錯誤,因為C::c對於父類B來說是私有成員,沒有權限,失敗。接著AB();,因為C::AB對於父類B來說是公共成員,沒有問題。

  接著是a.a = 10;,沒問題;b.a =

copyright © 萬盛學電腦網 all rights reserved