前文
下文中的出現的"目前域"為"目前作用域"的簡寫
namepsace在c++中是用來避免不同子產品下相同名字沖突的一種關鍵字,本文粗略的介紹了一下namespace的使用以及需要注意的地方:
- 1.可通過顯示指定namespace,或使用using引入符号的方式, 或使用using namepsace加載整個namespace的方式使用一個名稱空間下的符号
- 2.顯示指定符号所屬namespace的情況下,無論該表達式在哪裡都是按照namespace絕對路徑進行比對而不是相對路徑
- 3.using 指令是存在作用域和指令順序性的,允許同時使用多個namespace,同時使用多個namespace存在相同的符号并不會覆寫,但編譯會報ambigious,可通過顯示指定符号所屬namespace解決
- 4.using namespace相當于在目前域把namespace下的所有符号導入全局,如果目前域有局部變量,會出現局部變量覆寫全局變量的情況;
- 5.直接使用using引入變量A,相當于在目前域涉及到的A都是這個引入的A,不允許覆寫,同名,重複,否則報錯。
- 6.對于匿名namespace, 編譯器為每個不同編譯單元(.o檔案)生成的不同的namespace并使用它,可通過匿名空間實作類似于static的符号外部不可見性
- 7.namespace在編譯期完成後的本質就是給符号加字首
- 8.人生苦短,我還是顯示指定吧
正文
使用namespace
下文簡單的建立了一個名為n1的namespace,在其中放入了一個變量number,并在main()中使用了它
namespace n1{
int number = 1;
};
int main(){
//1.顯式指出使用那個namespace下的number
std::cout<<n1::number<<std::endl;
//2.引入n1::number後再使用它
using n1::number;
std::cout<<number<<std::endl;
return 0;
}
還有另一種方式擷取到n1::number,可以通過using namespace加載整個namespace進來,但這種方式有其複雜性(後文詳述),通常不推薦使用
namespace n1{
int number = 1;
}
int main(){
using namespace n1; //使用namespace n1
std::cout<<number<<std::endl;
return 0;
}
符号的namepsace查找規則
由于namespace是支援嵌套的,在顯式指出符号所屬的namespace的情況下,無論這個表達式放在什麼地方,都是按照絕對路徑從頭開始比對namespace,什麼意思呢, 如下:
namespace n1{
void fn(){
std::cout<<"1"; //這裡是去找std::cout, 而不是找 n1::std::cout
}
}
global namspace
因為編譯器為程式預設建立使用一個的global namespace(未顯示指定在其餘namespace下的定義生命變量都在global namespace下), 對于global namespace, 直接使用::, 前面什麼都不需要加, 一般而言全局域的::是可以省略的,因為所有建立的東西都是在全局名稱空間下,包括自己建立的namespace, 是以std::cout和::std::cout是一樣的。
int number = 0;
int main(){
std::cout<<::temp<<std::endl;
return 0;
}
作用域
下面的例子顯示了 using 是有作用域的,和一般的變量的作用域規則一樣
namespace n1{
int number = 1;
}
namespace n2{
int number = 2;
}
int main(){
{
using n1::number;
std::cout<<number<<std::endl; // 1 輸出n1::number
{
using n2::number;
std::cout<<number<<std::endl; // 2 輸出n2::number
}
std::cout<<number<<std::endl; // 1 輸出n1::number
}
return 0;
}
覆寫
當使用using namespace的時候,就會使用一個namespace,同時支援多個namespace的使用,在同時使用多個namespace的情況下,是會存在同名符号沖突的情況的,如下:
此時還是需要通過顯示的指出所屬namespace來解決
int number = 0; //global namespace
namespace n1{
int number = 1; //n1 namespace
}
int main(){
using namespace n1;
std::cout<<number<<std::endl; //由于同時存在兩個全局變量number,是以此處會報ambigious的錯誤
return 0;
}
但如果此處using的變量則沒有問題,其相當于在目前域引入了一個變量number
int number = 0;
namespace n1{
int number = 1;
}
int main(){
using n1::number; //聲明接下來要使用n1::number了,接下裡的number都是它,相當于在目前域引入了一個變量number
std::cout<<number<<std::endl; //沒問題,輸出1
return 0;
}
當使用一個namespace的時候,就相當于在目前域把該namespace的變量都導入全局中了,如往常,局部變量會覆寫全局變量
namespace n1{
int number = 1;
}
int main(){
int number = 0;
using namespace n1;
std::cout<<number<<std::endl;//輸出的結果為0, 局部變量覆寫了n1::number
return 0;
}
但是要注意,如果是引入變量到目前域,和之前所說的一樣相當于定義一個變量,編譯器嚴格控制了,不允許再定義相同的符号
namespace n1{
int number = 1;
}
int main(){
using n1::number; // error: redeclaration of ‘int number’
int number = 0;
return 0;
}
匿名namespace
當定義一個名稱空間時,可以忽略這個名稱空間的名稱,這種名稱空間又被稱為匿名namespace(匿名空間)
namespace {
int number;
}
編譯器在内部會為這個名稱空間生成一個唯一的名字,而且還會為這個匿名的名稱空間生成一條using指令。是以上面的代碼在效果上等同于:
namespace __UNIQUE_NAME_ {
int number;
}
using namespace __UNIQUE_NAME_;
匿名空間在不同的編譯單元下的__UNIQUE_NAME_ 是不同的,可以用來解決連結階段不同編譯單元(.o檔案)的符号同名現象,如下
**************f1.c***************
namespace{
int number;
}
void fn1(){}
**************f2.c***************
namespace{
int number;
}
void fn2(){}
**************main.c***************
int main(){
return 0;
}
**************編譯連結***************
⚡ root@acnszavl00033 ~/temp/test g++ -c f1.c
⚡ root@acnszavl00033 ~/temp/test g++ -c f2.c
⚡ root@acnszavl00033 ~/temp/test g++ -o a.out main.c f1.o f2.o
但如果去掉了f1.c和f2.c中的namespace就會報重定義的錯,因為在連結階段會發現有兩個同名符号(詳見編譯連結(1)的2.1 關于連結);namespace在編譯期後的本質,就是給不同的名稱空間下的符号加上字首, 而static将變量的可見性限制為.o檔案内部(而不是源檔案内部),因無法在程式設計時顯示調用我要xxx.o檔案的匿名空間進而使用到其符号,是以也常見google也使用匿名空間以替代static
跨檔案使用namespace
有時候我們希望跨檔案使用namespace,可以如下使用方式
**************fn1.h***************
namespace n1{
extern int number; //聲明
}
**************fn1.c***************
namespace n1{
int number = 1; //定義
}
**************main.c***************
#include"fn1.h"
#include<iostream>
int main(){
std::cout<<n1::number<<std::endl;
return 0;
}
當我們使用g++ main.c fn1.c去編譯的時候應該想到,其做了如下操作,
g++ -c main.c //生成main.o
g++ -c fn1.c //生成fn1.o
g++ main.o fn1.o
因為我們知道namespace在編譯期後的本質,就是給不同的名稱空間下的符号加上字首, 是以在連結階段,要連結符号字首相同,是以可以連結上