c++名稱空間入門
在 c++ 中,名稱可以是變數、函式、結構、列舉、類以及類和結構的成員
假設這樣一種情況,當一個班上有兩個名叫 WYB 的學生時,為了明確區分它們,我們在使用名字之外,不得不使用一些額外的資訊,比如他們的家庭住址,或者他們的其他身份等等。
同樣,當隨著專案的增大,名稱相互衝突的可能性也將增加。使用多個廠商的類庫是,可能會導致命名衝突,這樣,編譯器就無法判斷使用者想要使用哪個。
例如:兩個庫可能都定義了名為 HASH 和 TREE 的類,但定義的方式不相容,使用者可能希望使用一個庫的 HASH 類,而使用另一個庫的 TREE 類。這種衝突被稱為命名衝突問題。
因此,引入了名稱空間(namespace)
A 有關概念
宣告區域:可以在其中進行宣告的區域。例如在函式外宣告的變數,是全域性變數,其宣告區域為其宣告所在的檔案。
潛在作用域:從宣告的位置開始,到其宣告區域的結尾。
作用域:變數對於程式而言可見(可以呼叫)的範圍。
變數並非在其潛在作用域內的任何位置都是可見的,當變數間出現重名的情況下,作用域小的會遮蔽作用域大的。例如:
#include <iostream> using namespace std; // 全域性變數宣告int g = 20; int main () { // 區域性變數宣告 int g = 10; cout << g; return 0; }
當上面的程式碼被編譯和執行時,它會產生下列結果:
B 定義名稱空間
名稱空間的定義使用關鍵字namespace,後跟名稱空間的名稱,基本格式如下:
namespace namespace_name{ //程式碼宣告 }
例如:
#include<bits/stdc++.h> using namespace std; namespace WYB{ double pail; //變數宣告void fetch(int,int); //函式原型 int girlfriend;//變數宣告 struct parents{//結構宣告 //... }; void print(){ printf("Inside namespace_WYB\n"); } } namespace SEN{ void pail(int,double);//函式原型 double fetch; //變數宣告 int boyfriend; //變數宣告 void print(){ printf("Inside namespace_SEN\n"); } } int main(){ WYB::print(); SEN::print(); return 0; }
執行,得到:
上述程式碼說明了兩個問題:
- 任何名稱空間中的名稱都不會與其他名稱空間中的名稱發生衝突。即,WYB 中的 fetch函式 可以與 SEN 中的 fetch 變數共存。名稱空間中的宣告和定義規則同全域性宣告和定義規則相同。
- 可以通過作用域解析符 :: 來使用名稱空間中的名稱
另外,名稱空間是開放的,即可以吧名稱加入到已有的名稱空間中。例如,下列語句將名稱 waste 新增到 SEN 已有的名稱列表中:
namespace SEN{ char * waste(const char *); }
同樣,原來的 WYB 名稱空間為 fecth() 函式提供了原型,可以在該檔案後面(或另外一個檔案中)再次使用 WYB 名稱空間來提供該函式原型的函式體:
namespace WYB{ void fetch(int a,int b){ if(b==0){ throw "Division by zero condition!"; } } }
Tips:
未限定名稱(unqualified name):未被裝飾的名稱,如 pail
限定的名稱(qualified name):包含名稱空間的名稱,如 WYB:pail
C using宣告和編譯指令
真是麻煩!每次使用名稱的時候都要對它進行限定
平時沒有自己定義名稱空間的時候為什麼不用加 :: 呢?
原因是這樣一條語句:
using namespace std;
(std 即 c++ 標準庫)
我們並不希望每次使用名稱時都對它進行限定,因此C++提供了兩種機制(using宣告和using編譯指令)來簡化對名稱空間中名稱的使用。using宣告使特定的識別符號可用,而using編譯指令使整個名稱空間可用。
舉例:
int main(){
using namespace SEN; print(); using WYB::print;
print();
}
得到:
值得注意的是:(下列語句仍在main()中)
using WYB::print; print();
using namespace SEN; print();
其中using namespace 是using編譯指令
using ... 是using宣告
對於using宣告
將特定的名稱新增到它所屬的宣告區域中,例如main()中的using宣告 WYB::print將print新增到main()定義的宣告區域中。完成該聲明後,便可以使用名稱print代替WYB::print。
char fetch;
int main(){ using SEN::fetch;//put fetch into local namespace
double fetch;//Error!Already have a local fetch
cin>>fetch;//read a value into SEN::fetch cin>>::fetch;//read a value into global fetch return 0; }
上面的程式碼說明,由於using宣告將名稱新增到區域性宣告區域中,因此這個示例避免了將另一個區域性變數也命名為fetch。另外,和其他區域性變數一樣,fetch也將覆蓋同名的全域性變數。
在函式的外面使用using宣告時,將把名稱新增到全域性名稱空間中。
對於using編譯指令
using編譯指令由名稱空間名和它前面的關鍵字using namespace組成,它使該名稱空間中的所有名稱都可用,而不需要域名解析符,我們經常將這種格式用於名稱空間std
Tips:有關using編譯指令和using宣告,需要記住的一點是,他們增加了名稱衝突的可能性,但使用作用域解析符卻不會出現這樣的二義性問題,編譯器將不會允許這樣的情況。
二者之比較
使用using編譯指令匯入一個名稱空間中所有的名稱與使用多個using宣告是不一樣的,而更像是大量
使用作用域解析運算子。
使用using宣告時,就好像聲明瞭相應的名稱一樣。
如果某個名稱已經在函式中聲明瞭,則不能用using宣告匯入相同的名稱。然而,使用using編譯指令時,將進行名稱解析,就像在包含using宣告和名稱空間本身的最小宣告區域中聲明瞭名稱一樣。
在下面的示例中,名稱空間為全域性的。
如果使用using編譯指令匯入一個已經在函式中宣告的名稱,則區域性名稱將隱藏名稱空間名,就像隱藏同
名的全域性變數一樣。不過仍可以像下面的示例中那樣使用作用域解析運算子:
namespace Jill{ double bucket(double n){...} double fetch; struct Hill{...}; } char fetch;//global namespace int main(){ using namespace Jill; //import all namespaces names Hill Thrill; //create a type Jill::Hill structure double water=bucket(2);//use Jill::bucket(); double fetch; //not an error;hides Jill::fetch cin>>fetch; //read a value into the local fetch cin>>::fetch; //read a value into global fetch cin>>Jill::fetch; //read a value into Jill::fetch } int foom(){ Hill top; //Error Jill::Hill crest;//vaild }
在main()中,名稱Jill::fetch被放在區域性名稱空間中,但其作用域不是區域性的,因此不會覆蓋全域性的
fetch.
然而,區域性宣告的fetch將隱藏Jill:fetch和全域性fetch。.
然而,如果使用作用域解析運算子,則後兩個fetch變數都是可用的。
需要指出的另一點是,雖然函式中的using編譯指令將名稱空間的名稱視為在函式之外宣告的,但它
不會使得該檔案中的其他函式能夠使用這些名稱。因此,foom()函式不能使用未限定的識別符號Hill
Tips:假設名稱空間和宣告區域定義了相同的名稱。如果試圖使用using宣告將名稱空間的名稱匯入該宣告區域,則這兩個名稱會發生衝突,從而出錯。
如果使用using編譯指令將該名稱空間的名稱匯入該宣告區域,則區域性版本將隱藏名稱空間版本。
一般說來,使用using宣告比使用using編譯指令更安全,這是由於它只匯入指定的名稱。如果該名稱
與區域性名稱發生衝突,編譯器將發出指示。using編譯指令匯入所有名稱,包括可能並不需要的名稱。如果
與區域性名稱發生衝突,則區域性名稱將覆蓋名稱空間版本,而編譯器並不會發出警告。另外,名稱空間的開
放性意味著名稱空間的名稱可能分散在多個地方,這使得難以準確知道添加了哪些名稱。
D 名稱空間巢狀
名稱空間可以巢狀,您可以在一個名稱空間中定義另一個名稱空間,如下所示:
namespace namespace_name1 { // 程式碼宣告 namespace namespace_name2 { // 程式碼宣告 } }
可以通過使用 :: 運算子來訪問巢狀的名稱空間中的成員:
// 訪問 namespace_name2 中的成員 using namespace namespace_name1::namespace_name2; // 訪問 namespace:name1 中的成員 using namespace namespace_name1;
在上面的語句中,如果使用的是 namespace_name1,那麼在該範圍內 namespace_name2 中的元素也是可用的,如下所示:
#include <iostream> using namespace std; // 第一個名稱空間 namespace first_space{ void func(){ cout << "Inside first_space" << endl; } // 第二個名稱空間 namespace second_space{ void func(){ cout << "Inside second_space" << endl; } } } using namespace first_space::second_space; int main () { // 呼叫第二個名稱空間中的函式 func(); return 0; }
當上面的程式碼被編譯和執行時,它會產生下列結果:
E 引用
《C++ Primer Plus》 by Stephen Prata
C++ 名稱空間 by 菜鳥教程