本文轉(zhuǎn)載自:C++ 命名空間namespace
在C++
中,名稱(name
)可以是符號常量、變量、宏、函數(shù)、結(jié)構(gòu)、枚舉、類和對象等等。為了避免,在大規(guī)模程序的設計中,以及在程序員使用各種各樣的C++
庫時,這些標識符的命名發(fā)生沖突,標準C++
引入了關(guān)鍵字namespace
(命名空間/名字空間/名稱空間/名域),可以更好地控制標識符的作用域。
MFC
中并沒有使用命名空間,但是在.NET
框架、MC++
和C++/CLI
中,都大量使用了命名空間。
作用域與命名空間
相關(guān)概念
與命名空間相關(guān)的概念有:
聲明域(declaration region)—— 聲明標識符的區(qū)域。如在函數(shù)外面聲明的全局變量,它的聲明域為聲明所在的文件。在函數(shù)內(nèi)聲明的局部變量,它的聲明域為聲明所在的代碼塊(例如整個函數(shù)體或整個復合語句)。
潛在作用域(potential scope)—— 從聲明點開始,到聲明域的末尾的區(qū)域。因為C++采用的是先聲明后使用的原則,所以在聲明點之前的聲明域中,標識符是不能用的。即,標識符的潛在作用域,一般會小于其聲明域。
作用域(scope)—— 標識符對程序可見的范圍。標識符在其潛在作用域內(nèi),并非在任何地方都是可見的。例如,局部變量可以屏蔽全局變量、嵌套層次中的內(nèi)層變量可以屏蔽外層變量,從而被屏蔽的全局或外層變量在其倍屏蔽的區(qū)域內(nèi)是不可見的。所以,一個標識符的作用域可能小于其潛在作用域。
命名空間
命名空間(namespace
)是一種描述邏輯分組的機制,可以將按某些標準在邏輯上屬于同一個集團的聲明放在同一個命名空間中。
原來C++
標識符的作用域分成三級:代碼塊({……}
,如復合語句和函數(shù)體)、類和全局。現(xiàn)在,在其中的類和全局之間,標準C++
又添加了命名空間這一個作用域級別。
命名空間可以是全局的,也可以位于另一個命名空間之中,但是不能位于類和代碼塊中。所以,在命名空間中聲明的名稱(標識符),默認具有外部鏈接特性(除非它引用了常量)。
在所有命名空間之外,還存在一個全局命名空間,它對應于文件級的聲明域。因此,在命名空間機制中,原來的全局變量,現(xiàn)在被認為位于全局命名空間中。
標準C++
庫(不包括標準C
庫)中所包含的所有內(nèi)容(包括常量、變量、結(jié)構(gòu)、類和函數(shù)等)都被定義在命名空間std
(standard標準)中了。
定義命名空間
有兩種形式的命名空間——有名的和無名的。
命名空間的定義格式為:(取自C++標準文檔):
named-namespace-definition:
namespace identifier { namespace-body }
unnamed-namespace-definition:
namespace { namespace-body }
namespace-body: declaration-seq optional
即:(自己翻譯并改寫的)
有名的命名空間:
namespace 命名空間名 {
聲明序列(可選)
}
無名的命名空間:
namespace {
聲明序列(可選)
}
命名空間的成員,是在命名空間定義中的花括號內(nèi)聲明了的名稱。可以在命名空間的定義內(nèi),定義命名空間的成員(內(nèi)部定義)。也可以只在命名空間的定義內(nèi)聲明成員,而在命名空間的定義之外,定義命名空間的成員(外部定義)。
命名空間成員的外部定義的格式為:
命名空間名::成員名 ... ...
例如:
// out.h
namespace Outer { // 命名空間Outer的定義
int i; // 命名空間Outer的成員i的內(nèi)部定義
namespace Inner { // 子命名空間Inner的內(nèi)部定義
void f() { i++; } // 命名空間Inner的成員f()的內(nèi)部定義,其中的i為Outer::i
int i;
void g() { i++; } // 命名空間Inner的成員g()的內(nèi)部定義,其中的i為Inner::i
void h(); // 命名空間Inner的成員h()的聲明
}
void f(); // 命名空間Outer的成員f()的聲明
// namespace Inner2; // 錯誤,不能聲明子命名空間
}
void Outer::f() {i--; } // 命名空間Outer的成員f()的外部定義
void Outer::Inner::h() {i--; } // 命名空間Inner的成員h()的外部定義
// namespace Outer::Inner2 {/*... ...*/} // 錯誤,不能在外部定義子命名空間
注意:
不能在命名空間的定義中聲明(另一個嵌套的)子命名空間,只能在命名空間的定義中定義子命名空間。
也不能直接使用“命名空間名::成員名 ……”定義方式,為命名空間添加新成員,而必須先在命名空間的定義中添加新成員的聲明。
另外,命名空間是開放的,即可以隨時把新的成員名稱加入到已有的命名空間之中去。方法是,多次聲明和定義同一命名空間,每次添加自己的新成員和名稱。例如:
namespace A {
int i;
void f();
} // 現(xiàn)在A有成員i和f()
namespace A {
int j;
void g();
} // 現(xiàn)在A有成員i、f()、j和g()
還可以用多種方法,來組合現(xiàn)有的命名空間,讓它們?yōu)槲宜谩@纾?/p>
namespace My_lib {
using namespace His_string;
using namespace Her_vector;
using Your_list::List;
void my_f(String &, List &);
}
... ...
using namespace My_lib;
... ...
Vector<String> vs[5];
List<int> li[10];
my_f(vs[2], li[5]);
使用命名空間
作用域解析運算符(::
)
對命名空間中成員的引用,需要使用命名空間的作用域解析運算符::
。例如:
// out1.cpp
#include "out.h"
#include <iostream>
// 這里,我想記錄一下:
// 前面在namesapce Outer里面定義了一些變量,比如說i,我想說的是,這些變量在項目中的每個文件中
// 都是可見的,我們要訪問這些變量的話,只需要攜帶namsepace限定,比如說Outer::i
// 這也是c++優(yōu)于c的一個部分吧!c的某個文件的變量要全局可見的話,要用extern關(guān)鍵字來限定,顯得
// 特別麻煩。
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;
}
using
指令(using namespace
)
為了省去每次調(diào)用Inner
成員和標準庫的函數(shù)和對象時,都要添加Outer::Inner::
和std::
的麻煩,可以使用標準C++
的using
編譯指令來簡化對命名空間中的名稱的使用。格式為:
using namespace 命名空間名[::命名空間名……];
在這條語句之后,就可以直接使用該命名空間中的標識符,而不必寫前面的命名空間定位部分。因為using
指令,使所指定的整個命名空間中的所有成員都直接可用。例如:
// out2.cpp
#include "out.h"
#include <iostream>
// using namespace Outer; // 編譯錯誤,因為變量i和函數(shù)f()有名稱沖突
using namespace Outer::Inner;
using namespace std;
int main() {
Outer::i = 0;
Outer::f(); // Outer::i = -1;
f(); // Inner::f(),Outer::i = 0;
i = 0; // Inner::i
g(); // Inner::g(),Inner::i = 1;
h(); // Inner::h(),Inner::i = 0;
cout << "Hello, World!" << endl;
cout << "Outer::i = " << Outer::i << ", Inner::i = " << i << endl;
}
又例如:(.NET
框架)
using namespace System::Drawing::Imaging;
using namespace System::Window::Forms::Design::Behavior;
using聲明(using)
除了可以使用using
編譯指令(組合關(guān)鍵字using namespace
)外,還可以使用using
聲明來簡化對命名空間中的名稱的使用。格式為:
using 命名空間名::[命名空間名::……]成員名;
注意,關(guān)鍵字using
后面并沒有跟關(guān)鍵字namespace
,而且最后必須為命名空間的成員名(而在using
編譯指令的最后,必須為命名空間名)。
與using
指令不同的是,using
聲明只是把命名空間的特定成員的名稱,添加該聲明所在的區(qū)域中,使得該成員可以不需要采用,(多級)命名空間的作用域解析運算符來定位,而直接被使用。但是該命名空間的其他成員,仍然需要作用域解析運算符來定位。例如:
// out3.cpp
#include "out.h"
#include <iostream>
using namespace Outer; // 注意,此處無::Inner
using namespace std;
// using Inner::f; // 編譯錯誤,因為函數(shù)f()有名稱沖突
using Inner::g; // 此處省去Outer::,是因為Outer已經(jīng)被前面的using指令作用過了
using Inner::h;
int main() {
i = 0; // Outer::i
f(); // Outer::f(),Outer::i = -1;
Inner::f(); // Outer::i = 0;
Inner::i = 0;
g(); // Inner::g(),Inner::i = 1;
h(); // Inner::h(),Inner::i = 0;
cout << "Hello, World!" << endl;
cout << "Outer::i = " << i << ", Inner::i = " << Inner::i << endl;
}
using
指令與using
聲明的比較
可見,using
編譯指令和using
聲明,都可以簡化對命名空間中名稱的訪問。
using
指令使用后,可以一勞永逸,對整個命名空間的所有成員都有效,非常方便。而using
聲明,則必須對命名空間的不同成員名稱,一個一個地去聲明,非常麻煩。
但是,一般來說,使用using
聲明會更安全。因為,using
聲明只導入指定的名稱,如果該名稱與局部名稱發(fā)生沖突,編譯器會報錯。而using
指令導入整個命名空間中的所有成員的名稱,包括那些可能根本用不到的名稱,如果其中有名稱與局部名稱發(fā)生沖突,則編譯器并不會發(fā)出任何警告信息,而只是用局部名去自動覆蓋命名空間中的同名成員。特別是命名空間的開放性,使得一個命名空間的成員,可能分散在多個地方,程序員難以準確知道,別人到底為該命名空間添加了哪些名稱。
雖然使用命名空間的方法,有多種可供選擇。但是不能貪圖方便,一味使用using
指令,這樣就完全背離了設計命名空間的初衷,也失去了命名空間應該具有的防止名稱沖突的功能。
一般情況下,對偶爾使用的命名空間成員,應該使用命名空間的作用域解析運算符來直接給名稱定位。而對一個大命名空間中的經(jīng)常要使用的少數(shù)幾個成員,提倡使用using
聲明,而不應該使用using
編譯指令。只有需要反復使用同一個命名空間的許多數(shù)成員時,使用using
編譯指令,才被認為是可取的。
例如,如果一個程序(如上面的outi.cpp
)只使用一兩次cout
,而且也不使用std
命名空間中的其他成員,則可以使用命名空間的作用域解析運算符來直接定位。如:
#include <iostream>
... ...
std::cout << "Hello, World!" << std::endl;
std::cout << "Outer::i = " << Outer::i << ", Inner::i = " << Outer::Inner::i << std::endl;
又例如,如果一個程序要反復使用std
命名空間中的cin
、cout
和cerr
(如上面的outi.cpp
),而不怎么使用其他std
命名空間中的其他成員,則應該使用using
聲明而不是using
指令。如:
#include <iostream>
... ...
using std::cout;
cout << "Hello, World!" << endl;
cout << "Outer::i = " << Outer::i << ", Inner::i = " << Outer::Inner::i << endl;
命名空間的名稱
命名空間別名
標準C++
引入命名空間,主要是為了避免成員的名稱沖突。若果用戶都給自己的命名空間取簡短的名稱,那么這些(往往同是全局級的)命名空間本身,也可能發(fā)生名稱沖突。如果為了避免沖突,而為命名空間取很長的名稱,則使用起來就會不方便。這是一個典型的兩難問題。
標準C++
為此提供了一種解決方案——命名空間別名,格式為:
namespace 別名 = 命名空間名;
例如:(AT&T美國電話電報公司)
namespace American_Telephone_and_Telegraph { // 命名空間名太長
class String {
String(const char*);
// ... ...
}
}
American_Telephone_and_Telegraph::String s1 // 使用不方便
= new American_Telephone_and_Telegraph::String("Grieg");
namespace ATT = American_Telephone_and_Telegraph; // 定義別名
ATT::String s2 = new ATT::String("Bush"); // 使用方便
ATT::String s3 = new ATT::String("Nielsen");
無名命名空間
標準C++
引入命名空間,除了可以避免成員的名稱發(fā)生沖突之外,還可以使代碼保持局部性,從而保護代碼不被他人非法使用。如果你的目的主要是后者,而且又為替命名空間取一個好聽、有意義、且與別人的命名空間不重名的名稱而煩惱的話,標準C++
還允許你定義一個無名命名空間。你可以在當前編譯單元中(無名命名空間之外),直接使用無名命名空間中的成員名稱,但是在當前編譯單元之外,它又是不可見的。
無名命名空間的定義格式為:
namespace {
聲明序列(可選)
}
實際上,上面的定義等價于:(標準C++
中有一個隱含的使用指令)
namespace $$$ {
聲明序列(可選)
}
using namespace $$$;
例如:
namespace {
int i;
void f() {/*... ...*/}
}
int main() {
i = 0; // 可直接使用無名命名空間中的成員i
f(); // 可直接使用無名命名空間中的成員f()
}