C++名字空間詳解
程式碼編譯執行環境:VS2017+Win32+Debug
1.名字空間的由來
名字空間(Namespace)是由C++引入的,是一種新的作用域級別。原來C++識別符號的作用域從小到大分為四級:區域性作用域(程式碼塊)、函式作用域、類域和全域性作用域。如今,在類作用域和全域性作用域之間,C++標準又添加了名字空間域這一個作用域級別。
名字空間是ANSI C++引入的可以由使用者命名的作用域,用來處理程式中常見的同名衝突。
2.名字空間的作用
名字空間的作用主要是為了解決日益嚴重的名稱衝突問題。隨著可重用程式碼的增多,各種不同的程式碼體系中的識別符號之間同名的情況就會顯著增多。解決的辦法就是將不同的程式碼庫放到不同的名字空間中。
訪問一個具體的識別符號的時候,可以使用如下形式:space_name::identifier。即用作用域指示符“::”將名字空間的名稱和該空間下的識別符號連線起來,這要,即使使用同名的識別符號,由於它們處於不同的名字空間,也不會發生衝突。
有兩種形式的名稱空間——有名的和無名的。
定義格式為:
有名的名稱空間:
namespace 名稱空間名
{
宣告序列可選
}
匿名的名稱空間:
namespace
{
宣告序列可選
}
3.名字空間的注意要點
(1)一個名字空間可以在多個頭檔案或原始檔中實現,稱為分段定義。如果想在當前檔案訪問定義在另一個檔案中的同名名字空間內的成員變數,需要在當前檔案的名字空間內部進行申明。如標準C++庫中的所有元件都是在一個被稱為std的名字空間中宣告和定義的,這些元件當然分散在不同的標頭檔案和原始檔中。
(2)名字空間內部可以定義型別、函式、變數等內容,但名字空間不能定義在類和函式的內部。
(3)在一個名字空間中可以自由地訪問另一個名字空間的內容,因為名字空間並沒有保護級別的限制。
(4)雖然經常可以見到using namespace std;這樣的用法,我們也可以用同樣的方法將名字空間中的內容一次性“引入”到當前的名字空間中來,但這並不是一個值得推薦的用法。因為這樣做的相當於取消了名字空間的定義,使發生名稱衝突的機會增多。所以,用using單獨引入需要的內容,這樣會更有針對性。例如,要使用標準輸入物件,只需用using std::cin;就可以了。
(5)不能在名字空間的定義中宣告另一個巢狀的子名稱空間,只能在名稱空間中定義子名稱空間。
(6)名字空間的成員,可以在名稱空間的內部定義,也可以在名字空間的外部定義,但是要在名字空間進行宣告。
名稱空間成員的外部定義的格式為:
名字空間名::成員名 ……
(7)名字空間在進行分段定義時,不能定義同名的變數,否則連接出現重定義錯誤。因為名字空間不同於類,具有外部連線的特性。由於外部連線特性,請不要將名字空間定義在標頭檔案,因為當被不同的原始檔包含時,會出現重定義的錯誤。
結合以上幾點,觀察如下程式。
//main.cpp
#include <iostream>
namespace myspace1
{
extern int gvar;//內部宣告
extern int otherVar; //另一個檔案中同名名字空間中定義
using std::cout;
using std::endl;
class myclass
{
public:
void print()
{
cout<<"in space1,gvar="<<gvar<<endl;
}
};
}
namespace myspace2
{
using std::cout;
using std::endl;
int i=5;
class myclass
{
public:
void print(){
cout<<"in space2"<<endl;
}
};
namespace nestedspace
{
void ExternFunc();//內部宣告
}
}
//外部定義
int myspace1::gvar=1;
void myspace2::nestedspace::ExternFunc()
{
cout<<"in nestedspace"<<endl;
}
int main(int argc,char* argv[])
{
myspace1::myclass obj1;
obj1.print();
myspace2::myclass obj2;
obj2.print();
myspace2::nestedspace::ExternFunc();
std::cout<<myspace1::otherVar<<std::endl;
}
//sp2.cpp
namespace myspace1
{
int otherVar=3;
}
程式輸出結果是:
in space1,gvar=1
in space2
in nestedspace
3
(8)為了避免名稱空間的名字與其他的名稱空間同名,可以用較長的識別符號作為名稱空間的名字。但是書寫較長的名稱空間名時,有些冗餘,因此,我們可以在特定的上下文環境中給名稱空間起一個相對簡單的別名。
參考如下程式。
namespace MyNewlyCreatedSpace
{
void show()
{
std::cout<<"a function within a namespace"<<std::endl;
}
}
int main(int argc,char* argv[])
{
namespace sp=MyNewlyCreatedSpace;
sp::show();
}
4.匿名名字空間
4.1與static的共同作用
匿名名字空間提供了類似在全域性函式前加 static 修飾帶來的限制作用域的功能。它的這種特性可以被用在struct和class上, 而普通的static卻不能。比如,在兩個原始檔中定義了相同的全域性變數(或函式),就會發生重定義的錯誤。如果將它們宣告為全域性靜態變數(或函式)就可以避免重定義錯誤。在C++中,除了可以使用static關鍵字避免全域性變數(函式)的重定義錯誤,還可以通過匿名名字空間的方式實現。參考如下程式碼。
//main.cpp
#include <iostream>
using namespace std;
namespace
{
double dvar=1.8;
}
void show1()
{
cout<<"dvar:"<<dvar<<endl;
}
int main(int argc,char* argv[])
{
void show2();
show1();
show2();
}
//a.cpp
#include <iostream>
using namespace std;
double dvar=2.8;
void show2()
{
cout<<"dvar:"<<dvar<<endl;
}
程式輸出:
dvar:1.8
dvar:2.8
未命名的名字空間中定義的變數(或函式)只在包含該名字空間的檔案中可見,但其中的變數的生存期卻從程式開始到程式結束。如果有多個檔案包含未命名的名字空間,這些名字空間是不相關的,即使這些名字空間中定義了同名的變數(或函式),這些識別符號也代表不同的物件。
4.2與static的不同
通過匿名名字空間,同樣實現了對不同原始檔中同名全域性變數(函式)的保護,使它們不至於發生衝一定衝突。在這一點上,匿名名字空間和static的作用是相同的。
但是,用static修飾的變數(函式)具有內部連線特性,而具有內部連線特性的變數(函式)是不能用來例項化一個模板的。參考如下程式。
#include <iostream>
using namespace std;
template <char*p> class Example
{
public:
void display()
{
cout<<*p<<endl;
}
};
static char c='a';
int main(int argc,char* argv[])
{
Example<&c> a; //編譯出錯
a.display();
}
此程式無法通過編譯,因為靜態變數c不具有外部連線特性,因此不是真正的“全域性”變數。而類模板的非型別引數要求是編譯時常量表達式,或者是指標型別的引數要求指標指向的物件具有外部連線性。具體要求,參見C++標準關於模板非型別引數的要求:ISO相關標準官網。
為了實現既能保護全域性變數(函式)不受重定義錯誤的干擾,能夠使它們具有外部連線特性的目的,必須使用匿名名字空間機制。同樣是上面的這個程式,將char c=’a’;
置於匿名名字空間進行定義,即可通過編譯並執行。讀者可自行考證。
通過以上程式,可以看出匿名名字空間與static的區別:包含在匿名名字空間中的全域性變數(函式)具有外部連線特性,而用static修飾的全域性變數具有內部連線特性,不能用來例項化模板的非型別引數。
參考文獻
[1] C++高階進階教程.陳剛.武漢大學出版社
[2]c++匿名名字空間問題