程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> C++學習從零開始(一)

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

編輯:更多關於編程

      本文的中篇已經介紹了虛的意思,就是要間接獲得,並且舉例說明電視機的頻道就是讓人間接獲得電視台頻率的,因此其從這個意義上說是虛的,因為它可能操作失敗--某個頻道還未調好而導致一片雪花。並且說明了間接的好處,就是只用編好一段代碼(按5頻

      道),則每次執行它時可能有不同結果(今天5頻道被設置成中央5台,明天可以被定成中央2台),進而使得前面編的程序(按5頻道)顯得很靈活。注意虛之所以能夠很靈活是因為它一定通過“一種手段”來間接達到目的,如每個頻道記錄著一個頻率。但這是不夠的,一定還有“另一段代碼”能改變那種手段的結果(頻道記錄的頻率),如調台。

      先看虛繼承。它間接從子類的實例中獲得父類實例的所在位置,通過虛類表實現(這是“一種手段”),接著就必須能夠有“另一段代碼”來改變虛類表的值以表現其靈活性。首先可以自己來編寫這段代碼,但就要求清楚編譯器將虛類表放在什麼地方,而不同的編譯器有不同的實現方法,則這樣編寫的代碼兼容性很差。C++當然給出了“另一段代碼”,就是當某個類在同一個類繼承體系中被多次虛繼承時,就改變虛類表的值以使各子類間接獲得的父類實例是同一個。此操作的功能很差,僅僅只是節約內存而已。

      如:

      struct A { long a; };

      struct B : virtual public A { long b; }; struct C : virtual public A { long c; };

      struct D : public B, public C { long d; };

      這裡的D中有兩個虛類表,分別從B和C繼承而來,在D的構造函數中,編譯器會編寫必要的代碼以正確初始化D的兩個虛類表以使得通過B繼承的虛類表和通過C繼承的虛類表而獲得的A的實例是同一個。

      再看虛函數。它的地址被間接獲得,通過虛函數表實現(這是“一種手段”),接著就必須還能改變虛函數表的內容。同上,如果自己改寫,代碼的兼容性很差,而C++也給出了“另一段代碼”,和上面一樣,通過在派生類的構造函數中填寫虛函數表,根據當前派生類的情況來書寫虛函數表。它一定將某虛函數表填充為當前派生類下,類型、名字和原來被定義為虛函數的那個函數盡量匹配的函數的地址。

      如:

      struct A { virtual void ABC(), BCD( float ), ABC( float ); };

      struct B : public A { virtual void ABC(); };

      struct C : public B { void ABC( float ), BCD( float ); virtual float CCC( double ); };

      struct D : public C { void ABC(), ABC( float ), BCD( float ); };

      在A::A中,將兩個A::ABC和一個A::BCD的地址填寫到A的虛函數表中。

      在B::B中,將B::ABC和繼承來的B::BCD和B::ABC填充到B的虛函數表中。

      在C::C中,將C::ABC、C::BCD和繼承來的C::ABC填充到C的虛函數表中,並添加一個元素:C::CCC。

      在D::D中,將兩個D::ABC和一個D::BCD以及繼承來的D::CCC填充到D的虛函數表中。

      這裡的D是依次繼承自A、B、C,並沒有因為多重繼承而產生兩個虛函數表,其只有一個虛函數表。雖然D中的成員函數沒有用virtual修飾,但它們的地址依舊被填到D的虛函數表中,因為virtual只是表示使用那個成員函數時需要間接獲得其地址,與是否填寫到虛函數表中沒有關系。

      電視機為什麼要用頻道來間接獲得電視台的頻率?因為電視台的頻率人不容易記,並且如果知道一個頻率,慢慢地調整共諧電容的電容值以使電路達到那個頻率效率很低下。而做10組共諧電路,每組電路的電容值調好後就不再動,通過切換不同的共諧電路來實現快速轉換頻率。因此間接還可以提高效率。還有,5頻道本來是中央5台,後來看膩了把它換成中央2台,則同樣的動作(按5頻道)將產生不同的結果,“按5頻道”這個程序編得很靈活。

      由上面,至少可以知道:間接用於簡化操作、提高效率和增加靈活性。這裡提到的間接的三個用處都基於這麼一個想法--用“一種手段”來達到目的,用“另一段代碼”來實現上面提的用處。而C++提供的虛繼承和虛函數,只要使用虛繼承來的成員或虛函數就完成了“一種手段”。而要實現“另一段代碼”,從上面的說明中可以看出,需要通過派生的手段來達到。在派生類中定義和父類中聲明的虛函數原型相同的函數就可以改變虛函數表,而派生類的繼承體系中只有重復出現了被虛繼承的類才能改變虛類表,而且也只是都指向同一個被虛繼承的類的實例,遠沒有虛函數表的修改方便和靈活,因此虛繼承並不常用,而虛函數則被經常的使用。

      虛的使用

      由於C++中實現“虛”的方式需要借助派生的手段,而派生是生成類型,因此“虛”一般映射為類型上的間接,而不是上面頻道那種通過實例(一組共諧電路)來實現的間接。注意“簡化操作”實際就是指用函數映射復雜的操作進而簡化代碼的編寫,利用函數名映射的地址來間接執行相應的代碼,對於虛函數就是一種調用形式表現多種執行結果。而“提高效率”是一種算法上的改進,即頻道是通過重復十組共諧電路來實現的,正宗的空間換時間,不是類型上的間接可以實現的。因此C++中的“虛”就只能增加代碼的靈活性和簡化操作(對於上面提出的三個間接的好處)。

      比如動物會叫,不同的動物叫的方式不同,發出的聲音也不同,這就是在類型上需要通過“一種手段”(叫)來表現不同的效果(貓和狗的叫法不同),而這需要“另一段代碼”來實現,也就是通過派生來實現。即從類Animal派生類Cat和類Dog,通過將“叫(Gnar)”聲明為Animal中的虛函數,然後在Cat和Dog中各自再實現相應的Gnar成員函數。如上就實現了用Animal::Gnar的調用表現不同的效果。

      如下:

      Cat cat1, cat2; Dog dog; Animal *pA[] = { &cat1, &dog, &cat2 };

      for( unsigned long i = 0; i < sizeof( pA ); i++ ) pA[ i ]->Gnar();

      上面的容器pA記錄了一系列的Animal的實例的引用(關於引用,可參考《C++從零開始(八)》),其語義就是這是3個動物,至於是什麼不用管也不知道(就好象這台電視機有10個頻道,至於每個是什麼台則不知道),然後要求這3個動物每個都叫一次(調用

      Animal::Gnar),結果依次發出貓叫、狗叫和貓叫聲。這就是之前說的增加靈活性,也被稱作多態性,指同樣的Animal::Gnar調用,卻表現出不同的形態。上面的for循環不用再寫了,它就是“一種手段”,而欲改變它的表現效果,就再使用“另一段代碼”,也就是再派生不同的派生類,並把派生類的實例的引用放到數組pA中即可。

      因此一個類的成員函數被聲明為虛函數,表示這個類所映射的那種資源的相應功能應該是一個使用方法,而不是一個實現方式。如上面的“叫”,表示要動物“叫”不用給出參數,也沒有返回值,直接調用即可。因此再考慮之前的收音機和數字式收音機,其中有個功能為調台,則相應的函數應該聲明為虛函數,以表示要調台,就給出頻率增量或減量,而數字式的調台和普通的調台的實現方式很明顯的不同,但不管。意思就是說使用收音機的人不關心調台是如何實現的,只關心怎樣調台。因此,虛函數表示函數的定義不重要,重要的是函數的聲明,虛函數只有在派生類中實現有意義,父類給出虛函數的定義顯得多余。因此C++給出了一種特殊語法以允許不給出虛函數的定義,格式很簡單,在虛函數的聲明語句的後面加上“= 0”即可,被稱作純虛函數。

      如下:

      class Food; class Animal { public: virtual void Gnar() = 0, Eat( Food& ) = 0; };

      class Cat : public Animal { public: void Gnar(), Eat( Food& ); };

      class Dog : public Animal { void Gnar(), Eat( Food& ); };

      void Cat::Gnar(){} void Cat::Eat( Food& ){} void Dog::Gnar(){} void Dog::Eat

      ( Food& ){}

      void main() { Cat cat; Dog dog; Animal ani; }

      上面在聲明Animal::Gnar時在語句後面書寫“= 0”以表示它所映射的元素沒有定義。這和不書寫“= 0”有什麼區別?直接只聲明Animal::Gnar也可以不給出定義啊。注意上面的Animal ani;將報錯,因為在Animal::Animal中需要填充Animal的虛函數表,而它需要Animal::Gnar的地址。如果是普通的聲明,則這裡將不會報錯,因為編譯器會認為Animal::Gnar的定義在其他的文件中,後面的連接器會處理。但這裡由於使用了“= 0”,以告知編譯器它沒有定義,因此上面代碼編譯時就會失敗,編譯器已經認定沒有Animal::Gnar的定義。

      但如果在上面加上Animal::Gnar的定義會怎樣?Animal ani;依舊報錯,因為編譯器已經認定沒有Animal::Gnar的定義,連函數表都不會查看就否定Animal實例的生成,因此給出Animal::Gnar的定義也沒用。但映射元素Animal::Gnar現在的地址欄填寫了數字,因此當cat.Animal::Gnar();時沒有任何問題。如果不給出Animal::Gnar的定義,則cat.Animal::Gnar();依舊沒有問題,但連接時將報錯。

      注意上面的Dog::Gnar是private的,而Animal::Gnar是public的,結果dog.Gnar();將報錯,而dog.Animal::Gnar();卻沒有錯誤(由於它是虛函數結果還是調用Dog::Gnar),也就是前面所謂的public等與類型無關,只是一種語法罷了。還有class Food;,不用管它是聲明還是定義,只用看它提供了什麼信息,只有一個--有個類型名的名字為Food,是類型的自定義類型。而聲明Animal::Eat時,編譯器也只用知道Food是一個類型名而不是程序員不小心打錯字了就行了,因為這裡並沒有運用Food。

      上面的Animal被稱作純虛基類。基類就是類繼承體系中最上層的那個類;虛基類就是類帶有純虛成員函數;純虛基類就是沒有成員變量和非純虛成員函數,只有純虛成員函的基類。上面的Animal就定義了一種規則,也稱作一種協議或一個接口。即動物能夠Gnar,而且也能夠Eat,且Eat時必須給出一個Food的實例,表示動物能夠吃食物。即Animal這個類型成了一張說明書,說明動物具有的功能,它的實例變得沒有意義,而它由於使用純虛函數也正好不能生成實例。

      如果上面的Gner和Eat不是純虛函數呢?那麼它們都必須有定義,進而動物就不再是一個抽象概念,而可以有實例,則就可以有這麼一種動物,它是動物,但它又不是任何一種特定的動物(既不是貓也不是狗)。很明顯,這樣的語義和純虛基類表現出來的差很遠。

      那麼虛繼承呢?被虛繼承的類的成員將被間接操作,這就是它的“一種手段”,也就是說操作這個被虛繼承的類的成員,可能由於得到的偏移值不同而操作不同的內存。但對虛類表的修改又只限於如果重復出現,則修改成間接操作同一實例,因此從根本上虛繼承就是為了解決上篇所說的鯨魚有兩個饑餓度的問題,本身的意義就只是一種算法的實現。這導致在設計海洋生物和脯乳動物時,無法確定是否要虛繼承父類動物,而要看派生的類中是否會出現類似鯨魚那樣的情況,如果有,則倒過來再將海洋生物和脯乳動物設計成虛繼承自動物,這不是好現象。

      static(靜態)

      在《C++從零開始(五)》中說過,靜態就是每次運行都沒有變化,而動態就是每次運行都有可能變化。C++給出了static關字,和上面的public、virtual一樣,只是個語法標識而已,不是類型修飾符。它可作用於成員前面以表示這個成員對於每個實例來說都是不變的,如下:

      struct A { static long a; long b; static void ABC(); }; long A::a;

      void A::ABC() { a = 10; b = 0; }; void main() { A a; a.a = 10; a.b = 32; }

      上面的A::a就是結構A的靜態成員變量,A::ABC就是A的靜態成員函數。有什麼變化?上面的映射元素A::a的類型將不再是long A::而是long。同樣A::ABC的類型也變成void()而不是void( A:: )()。

      首先,成員要對它的類的實例來說都是靜態的,即成員變量對於每個實例所標識的內存的地址都相同,成員函數對於每個this參數進行修改的內存的地址都是不變的。上面把A::和A::ABC變成普通類型,而非偏移類型,就消除了它們對A的實例的依賴,進而實現上面說的靜態。

      由於上面對實例依賴的消除,即成員函數去掉this參數,成員變量映射的是一確切的內為A::a,類型為long,映射的地址並沒有給出,即還未定義,所以必須在全局空間中(即不在任何一個函數體內)再定義一遍,進而有long A::a;。同樣A::ABC的類型為void(),被去除了this參數,進而在A::ABC中的b = 10;等同於A::b = 10;,發現A::b是偏移類型,需要this參數,則等同於this->A::b = 10;。結果A::ABC沒有this參數,錯誤。而對於a = 10;,等同於A::a = 10;,而已經有這個變量,故沒任何問題。

      注意上面的a.a = 10;等同於a.A::a = 10;,而A::a不是偏移類型,那這裡不是應該報錯嗎?對此C++特別允許這種類型不匹配的現象,其中的“a.”等於沒有,因為這正是前面我們要表現的靜態成員。即A a, b; a.a = 10; b.a = 20;執行後,a.a為20,因為不管哪個實例,對成員A::a的操作都修改的同一個地址所標識的內存。

      什麼意義?它們和普通的變量的區別就是名字被A::限定,進而能表現出它們的是專用於類A的。比如房子,房子的門的高度和寬度都定好了,有兩個房子都是某個公司造的,它們的門的高度和寬度相同,因此門的高度和寬度就應該作為那個公司造的房子的靜態成員以記錄實際的高度和寬度,但它們並不需要因實例的不同而變化。

      除了成員,C++還提供了靜態局部變量。局部變量就是在函數體內的變量,被一對“{}”括起來,被限制了作用域的變量。對於函數,每次調用函數,由於函數體內的局部變量都是分配在棧上,按照之前說的,這些變量其實是一些相對值,則每次調用函數,可能由於棧的原因而導致實際對應的地址不同。

      如下:

      void ABC() { long a = 0; a++; } void BCD() { long d = 0; ABC(); }

      void main() { ABC(); BCD(); }

      上面main中調用ABC而產生的局部變量a所對應的地址和由於調用BCD,而在BCD中調用ABC而產生的a所對應的地址就不一樣,原理在《C++從零開始(十五)》中說明。因此靜態局部變量就表示那個變量的地址不管是通過什麼途徑調用它所在的函數,都不變化。如下:

      void ABC() { static long a = 0; a++; } void BCD() { long d = 0; d++; ABC(); }

      void main() { ABC(); BCD(); }

      上面的變量a的地址是固定值,而不再是原來那種相對值了。這樣從main中調用ABC和從BCD中調用ABC得到的變量a的地址是相同的。上面等同於下面:

      long g_ABC_a = 0; void ABC() { g_ABC_a++; } void BCD() { long d = 0; d++;

      ABC(); }

      void main() { ABC(); BCD(); }

      因此上面ABC中的靜態局部變量a的初始化實際在執行main之前就已經做了,而不是想象的在第一次調用ABC時才初始化,進而上面代碼執行完後,ABC中的a的值為2,因為ABC的兩次調用。

      它的意義?表示這個變量只在這個函數中才被使用,而它的生命期又需要超過函數的執行期。它並不能提供什麼語義(因為能提供的“在這個函數才被使用”使用局部變量就可以做到),只是當某些算法需要使用全局變量,而此時這個算法又被映射成了一個函數,則使用靜態變量具有很好的命名效果--既需要全局變量的生存期又應該有局部變量的語義。

      inline(嵌入)函數調用的效率較低,調用前需要將參數按照調用規則存放起來,然後傳遞存放參數的內存,還要記錄調用時的地址以保證函數執行完後能回到調用處(關於細節在《C++從零開始(十五)》中討論),但它能降低代碼的長度,尤其是函數體比較大而代碼中調用它的地方又比較多,可以大幅度減小代碼的長度(就好像循環10次,如果不寫循環語句,則需要將循環體內的代碼復制10遍)。但也可能倒過來,調用次數少而函數體較小,這時之所以還映射成函數是為了語義更明確。此時可能更注重的是執行效率而不是代碼長度,為此C++提供了inline關鍵字。

      在函數定義時,在定義語句的前面書寫inline即可,表示當調用這個函數時,在調用處不像原來那樣書寫存放、傳遞參數的代碼,而將此函數的函數體在調用處展開,就好像前面說的將循環體裡的代碼復制10遍一樣。這樣將不用做傳遞參數等工作,代碼的執行效率將提高,但最終生成的代碼的長度可能由於過多的展開而變長。如下:

      void ABCD(); void main() { ABCD(); } inline void ABCD() { long a = 0; a++; }

      上面的ABCD就是inline函數。注意ABCD的聲明並沒有書寫inline,因為inline並不是類型修飾符,它只是告訴編譯器在生成這個函數時,要多記錄一些信息,然後由連接器根據這些信息在連接前視情況展開它。注意是“視情況”,即編譯器可能足夠智能以至於在連接時發現對相應函數的調用太多而不適合展開進而不展開。對此,不同的編譯器給出了不同的處理方式,對於VC,其就提供了一個關鍵字__forceinline以表示相應函數必須展開,不用去管它被調用的情況。

      前面說過,對於在類型定義符中書寫的函數定義,編譯器將把它們看成inline函數。變成了inline函數後,就不用再由於多個中間文件都給出了函數的定義而不知應該選用哪個定義所產生的地址,因為所有調用這些函數的地方都不再需要函數的地址,函數將直接在那裡展開。

      const(常量)前面提到某公司造的房子的門的高度和寬度應該為靜態成員變量,但很明顯,在房子的實例存在的整個期間,門的高度和寬度都不會變化。C++對此專門提出了一種類型修飾符--const。它所修飾的類型表示那個類型所修飾的地址類型的數字不能被用於寫操作,

      即地址類型的數字如果是const類型將只能被讀,不能被修改。如:const long a = 10, b = 20; a++; a = 4;(注意不能cosnt long a;,因為後續代碼都不能修改a,而a的值又不能被改變,則a就沒有意義了)。這裡a++;和a = 4;都將報錯,因為a的類型為cosnt long,表示a的地址所對應的內存的值不能被改變,而a++;和a = 4;都欲改變這個值。

      由於const long是一個類型,因此也就很正常地有const long*,表示類型為const long的指針,因此按照類型匹配,有:const long *p = &b; p = &a; *p = 10;。這裡p = &a;按照類型匹配很正常,而p是常量的long類型的指針,沒有任何問題。但是*p = 10;將報錯,因為*p將p的數字直接轉換成地址類型,也就成了常量的long類型的地址類型,因此對它進行寫入操作錯誤。

      注意:const long* const p = &a; p = &a; *p = 10;,按照從左到右修飾的順序,上面的p的類型為const long* const,是常量的long類型的指針的常量,表示p的地址所對應的內存的值不能被修改,因此後邊的p = &a;將錯誤,違反const的意義。同樣*p = 10;也錯誤。不過可以:

      long a = 3, *const p = &a; p = &a; *p = 10;

      上面的p的類型為long* const,為long類型的常量,因此其必須被初始化。後續的p = &a;將報錯,因為p是long* const,但*p = 10;卻沒有任何問題,因為將long*轉成long後沒有任何問題。所以也有:

      const long a = 0; const long* const p = &a; const long* const *pp = &p;

      只要按照從左到右的修飾順序,而所有的const修飾均由於取內容操作符“*”的轉換而變成相應類型中指針類型修飾符“*”左邊的類型,因此*pp的類型是const long* const,*p的類型是const long。

      應注意C++還允許如下使用:

      struct A { long a, b; void ABC() const; };

      void A::ABC() const { a = 10; b = 10; }

      上面的A::ABC的類型為void( A:: )() const,其等同於:

      void A_ABC( const A *this ) { this->a = 10; this->b = 10; }

      因此上面的a = 10;和b = 10;將報錯,因為this的類型是const A*。上面的意思就是函數A::ABC中不能修改成員變量的值,因為各this的參數變成了const A*,但可以修改類的靜態成員變量的值,如:

      struct A { static long c; long a, b; void ABC() const; } long A::c;

      void A::ABC() const { a = b = 10; c = 20; }

      等同於:void A_ABC( const A *this ) { this->a = this->b = 10; A::c = 20; }。故依舊可以修改A::c的值。

      有什麼意義?出於篇幅,有關const的語義還請參考我寫的另一篇文章《語義的需要》。

      friend(友員)發信機具有發送電波的功能,收信機具有接收電波的功能,而發信機、收信機和電波這三個類,首先發信機由於將信息傳遞給電波而必定可以修改電波的一些成員變量,但電波的這些成員應該是protected,否則隨便一個石頭都能接收或修改電波所攜帶的信息。同樣,收信機要接收電波就需要能訪問電波的一些用protected修飾的成員,這樣就麻煩了。如果在電波中定義兩個公共成員函數,讓發信機和收信機可以通過它們來訪問被protected的成員,不就行了?這也正是許多人犯的毛病,既然發信機可以通過那個公共成員數修改電波的成員,那石頭就不能用那個成員函數修改電波嗎?這等於是原來沒有門,後來有個門卻不上鎖。為了消除這個問題,C++提出了友員的概念。

      在定義某個自定義類型時,在類型定義符“{}”中聲明一個自定義類型或一個函數,在聲明或定義語句的前面加上關鍵字friend即可。

      如:

      class Receiver; class Sender;

      class Wave { private: long b, c; friend class Receiver; friend class Sender; };

      上面就聲明了Wave的兩個友員類,以表示Receiver和Sender具備了Wave的資格,即如下:

      class A { private: long a; }; class Wave : public A { … };

      void Receiver::ABC() { Wave wav; wav.a = 10; wav.b = 10; wav.A::a = 10; }

      上面由於Receiver是Wave的友員類,所以在Receiver::ABC中可以直接訪問Wave::a、Wave::b,但wav.A::a = 10;就將報錯,因為A::a是A的私有成員,Wave不具備反問它的權限,而Receiver的權限等同於Wave,故權限不夠。

      同樣,也可有友員函數,即給出函數的聲明或定義,在語句前加上friend,如下:

      class Receiver { public: void ABC(); };

      class A { private: long a; friend void Receiver::ABC(); };

      這樣,就將Receiver::ABC作為了A的友員函數,則在Receiver::ABC中,具有類A具有的所有權限。

      應注意按照給出信息的思想,上面還可以如下:

      class A { private: long a; friend void Receiver::ABC() { long a = 0; } };

      這裡就定義了函數Receiver::ABC,由於是在類型定義符中定義的,前面已經說過,Receiver::ABC將被修飾為inline函數。那麼友員函數的意義呢?一個操作需要同時操作兩個資源中被保護了的成員,則這個操作應該被映射為友員函數。如蓋章需要用到文件和章兩個資源,則蓋章映射成的函數應該為文件和章的友員函數。

      名字空間

      前面說明了靜態成員變量,它的語義是專用於某個類而又獨立於類的實例,它與全局變量的關鍵不同就是名字多了個限定符(即“::”,表示從屬關系),如A::a是A的靜態成員變量,則A::a這個名字就可以表現出a從屬於A。因此為了表現這種從屬關系,就需要將變量定義為靜態成員變量。

      考慮一種情況,映射采礦。但是在陸地上采礦和在海底采礦很明顯地不同,那麼應該怎麼辦?映射兩個函數,名字分別為MiningOnLand和MiningOnSeabed。好,然後又需要映射在陸地勘探和在海底勘探,怎麼辦?映射為ProspectOnLand和ProspectOnSeabed。如果又需要映射在陸地鑽井和在海底鑽井,在陸地爆破和在海底爆破,怎麼辦?很明顯,這裡通過名字來表現語義已經顯得牽強了,而使用靜態成員函數則顯得更加不合理,為此C++提供了名字空間,格式為namespace <名字> { <各聲明或定義語句> }。其中的<名字>為定義的名字空間的名字,而<各聲明或定義語句>就是多條聲明或定義語句。如下:

      namespace OnLand { void Mining(); void Prospect(); void ArtesianWell(){} }

      namespace OnSeabed { void Mining(); void Prospect(); void ArtesianWell(){} }

      void OnLand::Mining() { long a = 0; a++; } void OnLand::Prospect() { long a = 0; a

      ++; }

      void OnSeabed::Mining() { long a = 0; a++; } void OnSeabed::Prospect() { long a =

      0; a++; }

      上面就定義了6個元素,每個的類型都為void()。注意上面OnLand::ArtesianWell和OnSeabed::ArtesianWell的定義直接寫在“{}”中,將是inline函數。這樣定義的六個變量它們的名字就帶有限定符,能夠從名字上體現從屬關系,語義表現得比原來更好,OnSeabed::Prospect就表示在海底勘探。注意也可以如下:

      namespace A { long b = 0; long a = 0; namespace B { long B = 0; float a = 0.0f } }

      namespace C { struct ABC { long a, b, c, d; void ABCD() { a = b = c = d =

      12; } } ab; }

      namespace D { void ABC(); void ABC() { long a = 0; a++; } extern float bd; }

      即名字空間裡面可以放任何聲明或定義語句,也可以用於修飾自定義結構,因此就可以C::ABC a; a.ABCD();。應注意C++還允許給名字空間別名,比如:namespace AB = C; AB::ABC a; a.ABCD();。這裡就給名字空間C另起了個名字AB,就好像之前提過的typedef一樣。

      還應注意自定義類型的定義的效果和名字空間很像,如struct A { long a; };將生成A::a,和名字空間一樣為映射元素的名字加上了限定符,但應該了解到結構A並不是名字空間,即namespace ABC = A;將失敗。名字空間就好像所有成員都是靜態成員的自定義結構。

      為了方便名字空間的使用,C++提供了using關鍵字,其後面接namespace和名字空間的名字,將把相應名字空間中的所有映射元素復制一份,但是去掉了名字前的所有限定符,並且這些元素的有效區域就在using所在的位置,如:

      void main() { { using namespace C; ABC a; a.ABCD(); } ABC b; b.ABCD(); }

      上面的ABC b;將失敗,因為using namespace C;的有效區域只在前面的“{}”內,出了就無效了,因此應該C::ABC b; b.ABCD();。有什麼用?方便書寫。因為每次調用OnLand::Prospect時都要寫OnLand::,顯得有點煩瑣,如果知道在某個區域內並不會用到OnSeabed的成員,則可以using namespace OnLand;以減小代碼的繁雜度。

      注意C++還提供了using更好的使用方式,即只希望去掉名字空間中的某一個映射元素的限定符而不用全部去掉,比如只去掉OnLand::Prospect而其它的保持,則可以:using OnLand::Prospect; Prospect(); Mining();。這裡的Mining();將失敗,而Prospect();將成功,因為using OnLand::Prospect;只去掉了OnLand::Prospect的限定符。

      至此基本上已經說明了C++的大部分內容,只是還剩下模板和異常沒有說明(還有自定義類型的操作符重載,出於篇幅,在《C++從零開始(十七)》中說明),它們帶的語義都很少,很大程度上就和switch語句一樣,只是一種算法的包裝而已。下篇介紹面向對象編程思想,並給出“世界”的概念以從語義出發來說明如何設計類及類的繼承體系

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved