A namespace is a scope.
C++ provides namespaces to prevent name conflicts.
A namespace is a mechanism for expressing logical grouping. That is, if some declarations logically belong together to some criteria(准則), they can be put in a common namespace to express that fact.
That is, the namespace is the mechanism(機制) for supporting module programming paradigm(范型).
C++名字空間是一種描述邏輯分組的機制。也就是說,如果有一些聲明按照某種准則在邏輯上屬於同一個模塊,就可以將它們放在同一個名字空間,以表明這個事實。名字空間對於模塊化的程序設計有重要作用。
例如:
// x.h namespace MyNamespace1 { int i; void func(); class CHello { public: void print(); } };
// y.h namespace MyNamespace2 { class CHello { public: void print(); } };
// z.cpp #include"x.h" #include"y.h" int main() { MyNamespace1:: CHello x; //聲明一個文件x.h中類MyClass的實例x MyNamespace2:: CHello y; //聲明一個文件x.h中類MyClass的實例x x. print(); //調用文件x.h中的函數print y. print(); //調用文件y.h中的函數print return 0; }
從上面可以看出,名字空間中可以定義變量、函數和類等自定義數據類型,它們具有相同的作用范圍。對於不同的名空間,可以定義相同的變量名、函數名、類名等,在使用的時候,只要在成員前區分開不同的名字空間就可以了。名字空間實質上是一個作用域,上例中通過引入名字空間解決了名字沖突。
名字空間的定義:
namespace是名字空間,可以防止多個文件有重復定義成員(變量,函數,類等)。
名字空間是一個作用域,其形式以關鍵字namespace開始,后接名字空間的名字,然后一對大括號內寫上名字空間的內容。
//point.h namespace spacepoint { struct point { int x; int y; }; void set(point& p, int i, int j) { p.x = i; p.y = j; } } //point.cpp using namespace spacepoint; int main() { point p; set(p,1,2); return 0; }
在頭文件point.h中定義了一個名字空間spacepoint,在point.cpp文件中若要訪問spacepoint中的成員,就必須加:using namespace spacepoint,表示要使用名字空間spacepoint,若沒加這句代碼,則會有編譯錯誤:
//point.cpp int main() { point p; //error C2065: “point”: 未聲明的標識符 set(p,1,2); // error C3861: “set”: 找不到標識符 return 0; }
名字空間的成員,是在名字空間定義中的花括號內聲明了的名稱。可以在名字空間的定義內,定義命名空間的成員(內部定義)。也可以只在名字空間的定義內聲明成員,而在名字空間的定義之外,定義名字空間的成員(外部定義)。命名空間成員的外部定義的格式為:
命名空間名::成員名 ……
例如:
// space.h namespace Outer // 命名空間Outer的定義 { int i; // 命名空間Outer的成員i的內部定義 namespace Inner // 子命名空間Inner的內部定義 { void f() { i++; } // 命名空間Inner的成員f()的內部定義,其中的i為Outer::i int i; void g() { i++; } // 命名空間Inner的成員g()的內部定義,其中的i為Inner::i void h(); // 命名空間Inner的成員h()的聲明 } void f(); // 命名空間Outer的成員f()的聲明 } void Outer::f() {i--;} // 命名空間Outer的成員f()的外部定義 void Outer::Inner::h() {i--;} // 命名空間Inner的成員h()的外部定義
域操作符:
The scope resolution operator (域解析符):: occurs between the namespaces name and the variable.
“::”就是作用域操作符,首先要有名字空間的概念。用戶聲明的名字空間成員名自動被加上前綴,名字空間名后面加上域操作符“::”,名字空間成員名由該名字空間名進行限定修飾。名字空間成員的聲明被隱藏在其名字空間中,除非我們為編譯器指定查找的聲明的名字空間,否則編譯器將在當前域及嵌套包含當前域的域中查找該名字的聲明。
#include "space.h" int main ( ) { Outer::i = 0; Outer::f(); // Outer::i = -1; Outer::Inner::f(); // Outer::i = 0; Outer::Inner::i = 0; Outer::Inner::g(); // Inner::i = 1; Outer::Inner::h(); // Inner::i = 0; std::cout << "Hello, World!" << std::endl; std::cout << "Outer::i = " << Outer::i << ", Inner::i = " << Outer::Inner::i << std::endl; }
上例中Outer::及Outer::Inner::,都是為編譯器制定了要查找的名字空間,名字空間Outer中函數:void f() { i++; },其中i為嵌套域中的Outer::i。對於函數Outer::Inner::g() { i++ },其中的i為當前域中的Outer::Inner::i。因為名字空間就是作用域,所有普通的作用域規則也對名字空間成立。因此,如果一個名字先前已經在空間或者外圍作用域里聲明過,就可以直接使用。
C++標准程序庫中的所有標識符都被定義於一個名為std的namespace中。由於namespace的概念,使用C++標准程序庫的任何標識符時,可以有三種選擇:
1、直接指定標識符。例如std::ostream而不是ostream。完整語句如下:
#include <iostream> std::cout << "hello!!"<< std::endl;
2、使用using關鍵字進行聲明
顯然,當某個名字在它自己的名字空間之外頻繁使用時,在反復寫它時都要加上名字空間來作限定詞,是一件令人厭煩的事情。這時,可以通過一個使用聲明而清楚掉,只需要在某個地方說明,在這個作用域其后的代碼中,使用此名字時可以自動解析出此名字所在的空間。例如:
#include <iostream> using std::cout; using std::endl;
此后在使用cout和endl時,都無需再加上名字空間前緣了:
cout <<"hello!!"<< endl;
3、最方便的就是使用指令using namespace std;
一個使用指令能把來自另一個名字空間的所有名字都變成在當前名字空間內可用,就像這些名字就在當前名字空間中一樣。例如,在一個名字空間內有命令 using namespace std; 則在此后的代碼中(當前空間最小局部內),對於所有名字都無需有名字空間前綴即可使用。
#include <iostream> using namespace std;
這樣命名空間std內定義的所有標識符都有效(曝光)。就好像它們被聲明為全局變量一樣。那么以上語句可以如下寫:
cout <<"hello!!"<< endl;
A namespace is a scope. Thus, “namespace is a very fundamental and relatively simple concept. The larger a program is, the more useful namespaces are to express logical separations of its parts.
Ideally(理想地), a namespace should
[1] express a logically coherent (相關的) set of features.
[2] not give users access to unrelated features,
[3] not impose a significant notational burden on users.(給用戶顯著需要注意的負擔)
無名的命名空間
在C++中 我們可以用未命名的名字空間(unnamed namespace)聲明一個局部於某一文件的實體,未命名的名字空間以關鍵字namespace開頭,同時該名字空間是沒有名字的,所以在關鍵字 namespace后面沒有名字,而在關鍵字 namespace后面使用花括號包含聲明塊。在一些情況下,名字空間的作用僅僅是為了保持代碼的局部性。
假設,在A.cpp中有如下定義:
//A.cpp namespace { void show(){ cout << "hello!" << endl; } }
這就像后面跟着using編譯指令一樣,即在該名字空間空聲明的名稱的作用域為:從聲明到該聲明區域末尾。這里show()函數在A.cpp中相當於全局函數,若還有show()函數的定義會出錯:
#include <iostream> using namespace std; void show() { cout << "hello!" << endl; } namespace { void show() { cout << "hello!!" << endl; } } int main() { show(); // error C2668: “show”: 對重載函數的調用不明確 return 0; }
同一個文件中可以有多個未命名的名字空間,但名字空間中不能有相同的成員:
#include <iostream> using namespace std; namespace { void show(){ cout << "hello!!" << endl; } } namespace { // error C2084: 函數“void `anonymous-namespace'::show(void)”已有主體 void show(){ cout << "hello!!!" << endl; } } int main() { show(); return 0; }
同一個文件中的多個未命名的名字空間,若它們位於不同的作用域,在它們名字空間中可以有相同的成員。下列中的兩個無命名空間中有相同的函數,但它們位於不同的作用域,若訪問aaa名字中的show函數,就需要加上作用域:aaa::show()。
#include <iostream> using namespace std; namespace //全局作用域下的無名字名字空間 { void show() { cout << "hello!!" << endl; } } namespace aaa { namespace //// aaa作用域下的無名字名字空間 { void show() { cout << "hello!!!" << endl; } } } int main() { show(); aaa::show(); return 0 }
名字空間的別名
名字空間的別名一般是為了方便使用。如果用戶給他們的名字空間名字起得太短,不同名字空間也可能引起沖突;而名字空間太長可能又變得不很實用。這時,可以通過在使用指令中給名字空間提供一個別名來改善情況。取別名的語法:
namespace別名 = 原空間名;
能使用別名來訪問原命名空間里的成員,但不能為源命名空間引入新的成員。例如:
#include <iostream> namespace mystd = std; mystd::cout << "hello!!" << mystd::endl;
在namespace mystd = std之后,可以用mystd來代表名字空間std。
組合和選擇
我們常常需要從現有的界面出發組合出新的界面,方法是在新界面中用使用using指令引入已有的界面。
namespace A { int i; void func1(){} void func2(){} } namespace B { int j; void func3(){} void func4(){} } namespace C { using namespace A; using namespace B; void func5(int m,int n){} } using namespace C; int main() { func1(); func3(); func5(i,j); return 0; }
在名字空間C中,通過using組合了名字空間A和B,形成了新的界面。這樣,使用using namespace C后,這樣不用using namespace C、using namespace B,就可以直接訪問func1()、func3()。
有時我們只是想從一個名字空間里選用幾個名字,通過使用聲明來做,可以使從名字空間里選擇一些特征的變得更加明確。
namespace A { int i; void func1(){} void func2(){} } namespace myA { using A::i; using A::func1; void func3(int a){} } using namespace myA; int main() { func1(); func3(i); return 0; }
名字空間和重載
如果從多個命名空間里引入同名而不同參的函數,且它們滿足構成重載的條件,則構成重載。
#include <iostream> using namespace std; namespace A { void f(char c) { cout<<"char is "<<c<<endl; } } namespace B { void f(int i) { cout<<"int is "<<i<<endl; } } int main() { using A::f; using B::f; f('a'); //A::f(char) f(10); //B::f(int) return 0; }
一般而言,名字空間內部的函數匹配按以下方式進行:
(1)找到候選函數集合。
(2)從候選集合中選擇可行函數。
(3)從可行集合中選擇一個最佳匹配。
名字查找
一個取T類型參數的函數常常與T類型本身定義在同一個名字空間里。因此,如果在使用一個函數的環境中無法找到它,我們就去查看它的參數所在的名字空間。
#include <iostream> using namespace std; namespace A { class Date{}; void func1(const Date&); } namespace B { class Time{}; void f(A::Date d,int i) { func1(d); // OK func1(i); // error C3861: “func1”: 找不到標識符 } void func2(Time t){} } int main() { B::Time t1; func2(t1); return 0; }
上例中,在名字空間B中調用的func1(d),函數func1不是在B中定義的,參數類型為Date,因此在Date所在的域A中進行查找,找到了void func1(const Date&)。但是調用func1(i)時,在作用域里沒有找到void format(int)型函數。
與顯式地使用限定比,這個查找規則能使程序員節省許多輸入,而且又不會以“使用指令”那樣的方式污染名字空間。這個規則對於運算符的運算對象和模板參數特別有用,因為在那里使用顯式限定是非常麻煩的。
名字空間是開放的
另外,命名空間是開放的,即可以隨時把新的成員名稱加入到已有的命名空間之中去。方法是,多次聲明和定義同一命名空間,每次添加自己的新成員和名稱。
namespace A { int i; void func1(); } // 現在A有成員i和func1() namespace A { int j; void func2(); } // 現在A有成員i、func1()、j和func2()