c++ namespace詳解


前文

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM