前文
下文中的出現的"當前域"為"當前作用域"的簡寫
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在編譯期后的本質,就是給不同的名稱空間下的符號加上前綴, 所以在鏈接階段,要鏈接符號前綴相同,所以可以鏈接上