全域性命名空間的劃分

2021-04-12 18:52:09 字數 3322 閱讀 4907

全域性空間最大的問題在於它本身僅有乙個。在大的軟體專案中,經常會有不少人把他們定義的名字都放在這個單一的空間中,從而不可避免地導致名字衝突。例如,假設

library1.h定義了一些常量,其中包括:

const double lib_version = 1.204;

類似的,library2.h也定義了:

const int lib_version = 3;

如果某個程式想同時包含library1.h和library2.h就會有問題。作為程式設計師,盡力使自己的程式庫不給別人帶來這些問題。例如,可預先想一些不大可能造成衝突的某種字首,加在每個全域性符號前。當然得承認,這樣組合起來的識別符號看起來不是那麼令人舒服。

另乙個比較好的方法是使用c++ namespace。namespace本質上和使用字首的方法一樣,只不過避免了別人總是看到字首而已。所以,不要這麼做:

const double sdmbook_version = 2.0;      // 在這個程式庫中, 每個符號以"sdm"開頭

// class sdmhandle ; 

sdmhandle& sdmgethandle();             // 為什麼函式要這樣宣告?  

而要這麼做:

namespace sdm ;

handle& gethandle();

}使用者於是可以通過三種方法來訪問這一名字空間裡的符號:將名字空間中的所有符號全部引入到某一使用者空間;將部分符號引入到某一使用者空間;或通過修飾符顯式地一次性使用某個符號:

void f1()

void f2()

void f3()

有些名字空間沒有名字。這種沒命名的名字空間一般用於限制名字空間內部元素的可見性。

名字空間帶來的最大的好處之一在於:潛在的二義不會造成錯誤。所以,從多個不同的名字空間引入同乙個符號名不會造成衝突(假如確實真的從不使用這個符號的話)。例如,除了名字空間sdm外,假如還要用到下面這個名字空間:

namespace acmewindowsystem

只要不引用符號handle,使用sdm和acmewindowsystem時就不會有衝突。假如真的要引用,可以明確地指明是哪個名字空間的handle:

void f()

假如用常規的基於標頭檔案的方法來做,只是簡單地包含sdm.h和acme.h,這樣的話,由於handle有多個定義,編譯將不能通過。

名字空間的概念加入到c++標準的時間相對較晚,所以有些人會認為它不太重要,可有可無。但這種想法是錯誤的,因為c++標準庫里幾乎所有的東西都存在於名字空間std之中。它有一種直接的影響方式:c++提供了那些沒有副檔名的標頭檔案,如, 等。

由於名字空間的概念引入的時間相對較晚,有些編譯器可能不支援。就算是這樣,那也沒理由汙染全域性名字空間,因為可以用struct來近似實現namespace。可以這樣做:先建立乙個結構用以儲存全域性符號名,然後將這些全域性符號名作為靜態成員放入結構中:

// 用於模擬名字空間的乙個結構的定義

struct sdm ;

static handle& gethandle();

};const double sdm::book_version = 2.0;      // 靜態成員的定義

現在,如果有人想訪問這些全域性符號名,只用簡單地在它們前面加上結構名作為字首:

void f()

如果全域性範圍內實際上沒有名字衝突,使用者就會覺得加修飾符麻煩而多餘。幸運的是,還是有辦法來讓使用者選擇使用它們或忽略它們。

對於型別名,可以用型別定義(typedef)來顯式地去掉空間引用。例如,假設結構s(模擬的名字空間)內有個型別名t,可以這樣用typedef來使得t成為s::t的同義詞:

typedef sdm::handle handle;

對於結構中的每個(靜態)物件x,可以提供乙個(全域性)引用x,並初始化為s::x:

const double& book_version = sdm::book_version;

處理函式的方法和處理物件一樣,但要注意,即使定義函式的引用是合法的,但**的維護者會更喜歡使用函式指標:

sdm::handle& (* const gethandle)() =      // gethandle是指向sdm::gethandle

sdm::gethandle;                         // 的const 指標

注意gethandle是乙個常指標。因為當然不想讓使用者將它指向別的什麼東西,而不是sdm::gethandle。

如果真想知道怎麼定義乙個函式的引用,看看下面:

sdm::handle& (&gethandle)() =      // gethandle是指向

sdm::gethandle;                  // sdm::gethandle的引用

除了初始化的方式外,函式的引用和函式的常指標在行為上完全相同,只是函式指標更易於理解。

有了上面的型別定義和引用,那些不會遭遇全域性名字衝突的使用者就會使用沒有修飾符的型別和物件名;相反,那些有全域性名字衝突的使用者就會忽略型別和引用的定義,代之以帶修飾符的符號名。還要注意的是,不是所有使用者都想使用這種簡寫名,所以要把型別定義和引用放在乙個單獨的標頭檔案中,不要把它和(模擬namespace的)結構的定義混在一起。

struct是namespace的很好的近似,但實際上還是相差很遠。它在很多方面很欠缺,其中很明顯的一點是對運算子的處理。如果運算子被定義為結構的靜態成員,它就只能通過函式呼叫來使用,而不能象常規的運算子所設計的那樣,可以通過自然的語法來使用:

// 定義乙個模擬名字空間的結構,結構內部包含widgets的型別

// 和函式。widgets物件支援operator+進行加法運算

struct widgets ;

static const widget operator+(const widget& lhs,const widget& rhs);

...

};// 為上面所述的widge和operator+ 建立全域性(無修飾符的)名稱

typedef widgets::widget widget;

const widget (* const operator+)(const widget&,        // 錯誤!

const widget&);       // operator+不能是指標名

widget w1, w2, sum;

sum = w1 + w2;                           // 錯誤! 本空間沒有宣告

// 引數為widgets 的operator+

sum = widgets::operator+(w1, w2);        // 合法, 但不是"自然"的語法

正因為這些限制,所以一旦編譯器支援,就要盡早使用真正的名字空間。

std空間和全域性命名空間

參考資料 注意不要盲目相信以下內容!不要盲目相信以下內容!在c 中,我們常用的標頭檔案為不帶 h 字尾的標頭檔案,而這些標頭檔案裡所包含和定義的識別符號在std空間中,所以我們使用c 時經常需要加上 using namespace std 而原c語言的標頭檔案為帶 h 字尾的標頭檔案,這些標頭檔案裡...

使用未命名的命名空間代替靜態全域性變數

當你的程式連線時,程式越大,越有可能在本地檔案中遇到名字衝突。在原始檔中宣告的函式或變數被轉化成本地的編譯單元,可能與另乙個編譯單元中宣告的相同函式或變數產生名字衝突。這是因為所有沒有宣告為 static 的符號具有外部鏈結性,所以它們的名字在整個程式中必須是唯一的。針對該問題,典型的c 解決方法是...

C 命名空間 預設命名空間與匿名命名空間

2.匿名命名空間 預設命名空間也叫全域性命名空間,所有沒有置於某一具體命名空間下的 即在預設命名空間下,例如func main 函式。include void func void intmain int argc,char ar void func void 需要注意的是 這個就是類似於c語言裡簡單...