標籤:ams 大型 double 標準庫 細節 地方 聲明和定義 comment htm
from http://www.cnblogs.com/yabin/p/6411746.html1 模組化和介面
任何實際程式都是有一些部分組成的。通過將程式進行模組化可以使我們的程式更加清晰,有助於多人合作和維護。
將一個程式進行模組化以後,當其中一個模組調用另一個模組時,它不需要知道其具體實現,只需要調用它提供的介面即可。因此一個模組應該是由兩個部分組成:具體實現和提供給外部的介面。
2 命名空間2.1命名空間的作用
命名空間相當於一個容器,它裡麵包含了邏輯結構上互相關聯的一組類、模板、函數等。也就是說如果某些“對象”在邏輯上有關係,我們就可以將它們放到一個命名空間裡用以和外界進行區分。命名空間一個顯著的特點是命名空間內的變數(類等)名可以和命名空間以外的重名。這可以用來將不同人寫的代碼進行整合。
命名空間的使用格式如下:
namespace A{ void Fun1(){...}; void Fun2(){...};}
上面的組織形式我們將函數的具體實現和聲明放到了一起,有時候我們並不想看到函數的具體實現,只希望能一眼看到的全部都是函數的介面介面。我們可以採用如下的方式將函數的介面和具體實現分開。
namespace A{ void Fun1(); void FUn1();}void A::Fun1(){/*...*/}void A::Fun2(){/*...*/}
- 如果一個函數的定義沒有在其對應的命名空間裡,必須要使用範圍解析符::來指定函數的命名空間。
- 不可以在命名空間以外定義一個命名空間中不存在的新成員。例如:
void A:Fun3(); //錯誤,A裡並沒有Fun3()
- 一個良好的程式應該將程式中的所有實體(變數,類,函數)都放到某個命名空間裡。當然除了main()函數之外。
2.2命名空間範圍規則
命名空間是一個範圍,因此它具有普通範圍的規則。
- 如果一個變數之前在該命名空間或其外圍範圍聲明過,則它可以直接使用。
- 使用來自另外一個命名空間的變數,需要加上範圍解析符。
- 如果頻繁的使用另外一個命名空間的實體,可以使用using將其引入,之後再使用時就不用加範圍解析符了。
double A::Fun(){ using B::Fun1; //使用B命名空間的函數Fun1; using C::Var1; //使用C命名空間的變數Var1; void Fun1(Var1); //B::Fun1}
- 在函數中使用using則外部變數的範圍限於函數,如果在命名空間中使用using,則作用範圍擴大到整個命名空間。如果使用到一個命名空間中的多個實體,可以直接用using namespace Name將命名空間中所有的實體都引入,但是讓一個命名空間的所有實體都可以被另一個命名空間訪問並不是一個安全的做法,應盡量避免。
namespace A{ using namespace B; //使用命名空間B中所有實體}
- 如果使用using引入的實體之間或者和本命名空間中的實體有重名的。那麼加上範圍解析符能協助我們更好的對它們進行區分。
2.3多重介面
有時候我們同一個命名空間在面向不同的使用者時,可能需要提供不同的介面。比如我們有一個命名空間裡面定義了關於串口的一些實體。我們給一個開發中的程式提供的介面可能包括:開啟串口,設定傳輸速率,設定校正位等。但是面向一個終端使用者時,我們可能只需要給他提供一個開啟串口介面就夠了。這便是使用多重介面的意義。
- 實現多重介面的方法有很多,首先可能想到的是使用不同的命名空間。
namespace A{ void Fun1(); void Fun2(); void Fun3();}namespace A_Interface1{ using A::Fun1;}
- 上面介面實現的過程中,A_Interface1和A有著非常強的關聯,修改A中的Fun1會使A_Interface1中的Fun1也修改。有時候我們可能不需要這麼強的關聯性,讓A_Interface1中的函數有著一定的可控性,可以使用下面介面實現的方式。
namespace A{ void Fun1(); void Fun2(); void Fun3();}namespace A_Interface1{ void Fun1(){A::Fun1();}}
這裡為了書寫方便將A_Interface1中Fun1的聲明和定義放到一起了。上面這種介面實現形式保證了A_Interface1中的Fun1有一定的自主性,當A中的Fun1改變時,我們可以在A_Interface1中的Fun1進行進一步的調整,或者乾脆不用A中的Fun1重新實現Fun1.這種介面處理方式去除了一定的耦合性,基本已經能夠滿足大部分需求了。
3.當然我們也可以不重新定義一個命名空間直接使用原來命名空間的名字,但是這樣容易誤導程式設計者以外的人(讓看到這個介面的人以外該命名空間只有聲明的這些實體)。
namespace A{ void Fun1(); void Fun2(); void Fun3();}//使用介面的檔案包含下面這個聲明或其所在的檔案namespace A{ void Fun1();}
2.4無名命名空間
我們知道不同命名空間的變數名可以重複,這有助於第三方將兩個不同人寫的代碼進行整合。有時候我們並不想我們的某些代碼被其他人進行整合,但是也想利用命名空間的優勢——可以讓變數名重複。這時候使用無名命名空間就很有價值了:第一沒有名字,其他地方無法引用進去;第二因為是命名空間它裡面的變數可以和其他命名空間中變數的名字重複。
無名命名空間可以在本編譯單元(所在檔案)處調用,沒有這一規則就永遠都用不到了。需要注意的是,不同編譯單元中的無名命名空間不同。
2.5編譯器的名字尋找
- 一個函數Fun1如果有一個參數為T類型,那麼一般情況下類型T和函數Fun1都在同一個命名空間裡。編譯器在調用使用T為參數的函數時,也會隱式的從T(和其基類)所在命名空間尋找被調用函數。編譯器這種隱式的尋找過程可以使程式員省掉許多顯式的限定符或using指令。而且這個尋找機制在對象的運算子運算和模板參數中特別有用。
namespace A{ class TypeA{...}; void Fun1(TypeA a){...};}void Fun(A::TypeA a){ Fun1(a); //可以,會在TypeA所在命名空間A找到Fun1 Fun1(2); //不行}
當然,命名空間本身必須在範圍裡,函數也必須在尋找和使用之前聲明。
- 如果調用的函數Fun1中有兩個參數,且這兩個參數所在命名空間都找到了Fun1函數。那麼就會調用重載解析規則。
namespace A{ class TypeA{...}; bool operator==(const A&,std::string&);}void Fun(A::TypeA a,std::string str){ if(a == str) {...}}
在上例中運算式a==str中有兩種資料類型,且這兩種資料類型所在的命名空間A,std都定義了operator==(string的運算子的定義在std中),於是重載解析規則就會被調用。由於std::operator不以TypeA為參數,所有最後編譯器會調用A::operator==.
3. 當一個類的成員調用一個函數時,編譯器尋找函數時偏向於在同一類和其基類中尋找函數,而不是在被調用函數其他參數類型所在的命名空間尋找。但這一規則對運算子(如+,-)尋找並不適用。
2.6命名空間的別名
我們在給命名空間取名字的時候,如果太短(比如上面的A)很可能出現衝突。起太長又太麻煩。這時候我們將長名字的命名空間在合適的地方取個別名可能會更好些。格式如下:
namespace A = LongNameNamespaceA;
這種替換的方式和C語言中的宏定義非常相似,因此別名另外一個特別用於的地方就是使代碼對命名空間的依賴降低。比如我們有一個大型程式依賴於一個命名空間LibA,現在需要版本升級將所有引用LibA中的代碼替換為LibA_Plus.如果使用起別名的方式,我們不用再代碼中逐個尋找將LibA::替換為LibA_Plus.而是簡單的在命名空間的別名定義處稍作修改即可。
namespace A = LibA;//替換為namespace A = LibA_Plus;
但也需要注意過多的使用命名空間別名也容易造成混亂。
2.7命名空間的組合
前面我們說了命名空間裡可以包含其他的命名空間,下面給出命名空間組合時的一些具體細節。
- 再次強調在命名空間裡沒有聲明的實體,不能在別處定義。即使這個實體在其包含的命名空間裡聲明過了。
namespace A{ using namespace B;}namespace B{ fill(char C);}A::fill(char C) //錯誤 A中並沒有聲明fill函數{...}
- 引入一個命名空間的實體時可以引入它的所有重載。
namespace B{ class String{...}; String operator+(const String&,const String&); String operator+(const String&,const char*);}namespace A{ using B::String; using B::operator+; //使用B中所定義的全部(兩個)+運算}
- 顯示的使用using關鍵字可以改變重載的順序。
namespace LibA{ class String{...}; template<class T>class Vector{...}; //其他實體}namespace LibB{ class String{...}; template<class T> class Vector{...}; //其他實體}namespace LibC{ using namespace LibA; using namespace LibB; using LibA::String; //偏向使用LibA中的String using LibB::Vector; //偏向使用LIbB中的Vector //其他實體 }
- 當然如果在LibC中聲明了String或者Vector的話就沒有LibA和LibB什麼關係了。
- 如果我們想要使用被重載覆蓋的LibAVector和LibBString.除了加上範圍解析符之外,可以使用typedef的形式和繼承的形式。
template<class T>class A_Vector:public LibA::Vector<T>{...};typedef LibB::String B_String;
- 對標準C庫繼續支援。以printf為例,在C標頭檔中使用std命名空間將stdio.h進行包裹。
//stdio.hnamespace std{ int printf(const char*...);}using namespace std; //包含標頭檔後不需要再重複這句話了//...#include <cstdio> //對新庫也進行包含using std::printf;
C++中提供了新的標頭檔,標頭檔中沒有使用using指令,這使不希望全部的標準庫實體都隱式的使用成為了可能。
//cstdio.hnamespace std{ int printf(const char* ...); //...}
- 命名空間是開放的,你可以在不同的檔案中或同一個檔案中的不同地方給命名空間加新的成員。也非常推薦使用這種方法將命名空間進一步分類,而不是將所有的實體都放到一個大塊的命名空間中。
//file1.cnamespace A{ int a;}//其他代碼namespace A{ int b;}//file2.hnamespace A{ int c;}
C++-命名空間