天天看點

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在編譯期後的本質,就是給不同的名稱空間下的符号加上字首, 是以在連結階段,要連結符号字首相同,是以可以連結上