1. 程式人生 > 實用技巧 >c++名稱空間入門

c++名稱空間入門

在 c++ 中,名稱可以是變數、函式、結構、列舉、類以及類和結構的成員

假設這樣一種情況,當一個班上有兩個名叫 WYB 的學生時,為了明確區分它們,我們在使用名字之外,不得不使用一些額外的資訊,比如他們的家庭住址,或者他們的其他身份等等。

同樣,當隨著專案的增大,名稱相互衝突的可能性也將增加。使用多個廠商的類庫是,可能會導致命名衝突,這樣,編譯器就無法判斷使用者想要使用哪個。

例如:兩個庫可能都定義了名為 HASH 和 TREE 的類,但定義的方式不相容,使用者可能希望使用一個庫的 HASH 類,而使用另一個庫的 TREE 類。這種衝突被稱為命名衝突問題。

因此,引入了名稱空間(namespace)

這個概念,專門用於解決上面的問題,它可作為附加資訊來區分不同庫中相同名稱的函式、類、變數等。使用了名稱空間即定義了上下文。本質上,名稱空間就是定義了一個範圍。

A 有關概念

宣告區域:可以在其中進行宣告的區域。例如在函式外宣告的變數,是全域性變數,其宣告區域為其宣告所在的檔案。

潛在作用域:從宣告的位置開始,到其宣告區域的結尾。

作用域:變數對於程式而言可見(可以呼叫)的範圍。

變數並非在其潛在作用域內的任何位置都是可見的,當變數間出現重名的情況下,作用域小的會遮蔽作用域大的。例如:

#include <iostream>
using namespace std;

// 全域性變數宣告
int g = 20; int main () { // 區域性變數宣告 int g = 10; cout << g; return 0; }

當上面的程式碼被編譯和執行時,它會產生下列結果:

B 定義名稱空間

名稱空間的定義使用關鍵字namespace,後跟名稱空間的名稱,基本格式如下:

namespace namespace_name{
     //程式碼宣告
}

例如:

#include<bits/stdc++.h>
using namespace std;

namespace WYB{
    double pail;   //變數宣告
void fetch(int,int); //函式原型 int girlfriend;//變數宣告 struct parents{//結構宣告 //... }; void print(){ printf("Inside namespace_WYB\n"); } } namespace SEN{ void pail(int,double);//函式原型 double fetch; //變數宣告 int boyfriend; //變數宣告 void print(){ printf("Inside namespace_SEN\n"); } } int main(){ WYB::print(); SEN::print(); return 0; }

執行,得到:

上述程式碼說明了兩個問題:

  1. 任何名稱空間中的名稱都不會與其他名稱空間中的名稱發生衝突。即,WYB 中的 fetch函式 可以與 SEN 中的 fetch 變數共存。名稱空間中的宣告和定義規則同全域性宣告和定義規則相同。
  2. 可以通過作用域解析符 :: 來使用名稱空間中的名稱

另外,名稱空間是開放的,即可以吧名稱加入到已有的名稱空間中。例如,下列語句將名稱 waste 新增到 SEN 已有的名稱列表中:

namespace SEN{
    char * waste(const char *);
}

同樣,原來的 WYB 名稱空間為 fecth() 函式提供了原型,可以在該檔案後面(或另外一個檔案中)再次使用 WYB 名稱空間來提供該函式原型的函式體:

namespace WYB{
    void fetch(int a,int b){
        if(b==0){
              throw "Division by zero condition!";
        }
    }
} 

Tips:

未限定名稱(unqualified name):未被裝飾的名稱,如 pail

限定的名稱(qualified name):包含名稱空間的名稱,如 WYB:pail

C using宣告和編譯指令

真是麻煩!每次使用名稱的時候都要對它進行限定

平時沒有自己定義名稱空間的時候為什麼不用加 :: 呢?

原因是這樣一條語句:

using namespace std;

(std 即 c++ 標準庫)

我們並不希望每次使用名稱時都對它進行限定,因此C++提供了兩種機制(using宣告和using編譯指令)來簡化對名稱空間中名稱的使用。using宣告使特定的識別符號可用,而using編譯指令使整個名稱空間可用。

舉例:

int main(){
using
namespace SEN; print(); using WYB::print;
print();
}

得到:

值得注意的是:(下列語句仍在main()中)

using WYB::print; 
print();
using namespace SEN; print();

其中using namespace 是using編譯指令

using ... 是using宣告

對於using宣告

將特定的名稱新增到它所屬的宣告區域中,例如main()中的using宣告 WYB::print將print新增到main()定義的宣告區域中。完成該聲明後,便可以使用名稱print代替WYB::print。

char fetch;
int
main(){ using SEN::fetch;//put fetch into local namespace
   double fetch;//Error!Already have a local fetch
cin>>fetch;//read a value into SEN::fetch cin>>::fetch;//read a value into global fetch return 0; }

上面的程式碼說明,由於using宣告將名稱新增到區域性宣告區域中,因此這個示例避免了將另一個區域性變數也命名為fetch。另外,和其他區域性變數一樣,fetch也將覆蓋同名的全域性變數。

在函式的外面使用using宣告時,將把名稱新增到全域性名稱空間中。

對於using編譯指令

using編譯指令由名稱空間名和它前面的關鍵字using namespace組成,它使該名稱空間中的所有名稱都可用,而不需要域名解析符,我們經常將這種格式用於名稱空間std

Tips:有關using編譯指令和using宣告,需要記住的一點是,他們增加了名稱衝突的可能性,但使用作用域解析符卻不會出現這樣的二義性問題,編譯器將不會允許這樣的情況。

二者之比較

使用using編譯指令匯入一個名稱空間中所有的名稱與使用多個using宣告是不一樣的,而更像是大量
使用作用域解析運算子。

使用using宣告時,就好像聲明瞭相應的名稱一樣。

如果某個名稱已經在函式中聲明瞭,則不能用using宣告匯入相同的名稱。然而,使用using編譯指令時,將進行名稱解析,就像在包含using宣告和名稱空間本身的最小宣告區域中聲明瞭名稱一樣。

在下面的示例中,名稱空間為全域性的。

如果使用using編譯指令匯入一個已經在函式中宣告的名稱,則區域性名稱將隱藏名稱空間名,就像隱藏同
名的全域性變數一樣。不過仍可以像下面的示例中那樣使用作用域解析運算子:

namespace Jill{
    double bucket(double n){...}
    double fetch;
    struct Hill{...};
} 
char fetch;//global namespace
int main(){
    using namespace Jill;  //import all namespaces names
    Hill Thrill;           //create a type Jill::Hill structure
    double water=bucket(2);//use Jill::bucket();
    double fetch;          //not an error;hides Jill::fetch
    cin>>fetch;            //read a value into the local fetch
    cin>>::fetch;          //read a value into global fetch
    cin>>Jill::fetch;      //read a value into Jill::fetch
}
int foom(){
    Hill top;        //Error
    Jill::Hill crest;//vaild
}

在main()中,名稱Jill::fetch被放在區域性名稱空間中,但其作用域不是區域性的,因此不會覆蓋全域性的
fetch.

然而,區域性宣告的fetch將隱藏Jill:fetch和全域性fetch。.

然而,如果使用作用域解析運算子,則後兩個fetch變數都是可用的。

需要指出的另一點是,雖然函式中的using編譯指令將名稱空間的名稱視為在函式之外宣告的,但它

不會使得該檔案中的其他函式能夠使用這些名稱。因此,foom()函式不能使用未限定的識別符號Hill

Tips:假設名稱空間和宣告區域定義了相同的名稱。如果試圖使用using宣告將名稱空間的名稱匯入該宣告區域,則這兩個名稱會發生衝突,從而出錯。

如果使用using編譯指令將該名稱空間的名稱匯入該宣告區域,則區域性版本將隱藏名稱空間版本。

一般說來,使用using宣告比使用using編譯指令更安全,這是由於它只匯入指定的名稱。如果該名稱
與區域性名稱發生衝突,編譯器將發出指示。using編譯指令匯入所有名稱,包括可能並不需要的名稱。如果
與區域性名稱發生衝突,則區域性名稱將覆蓋名稱空間版本,而編譯器並不會發出警告。另外,名稱空間的開
放性意味著名稱空間的名稱可能分散在多個地方,這使得難以準確知道添加了哪些名稱。

D 名稱空間巢狀

名稱空間可以巢狀,您可以在一個名稱空間中定義另一個名稱空間,如下所示:

namespace namespace_name1 {
   // 程式碼宣告
   namespace namespace_name2 {
      // 程式碼宣告
   }
}

可以通過使用 :: 運算子來訪問巢狀的名稱空間中的成員:

// 訪問 namespace_name2 中的成員
using namespace namespace_name1::namespace_name2;
 
// 訪問 namespace:name1 中的成員
using namespace namespace_name1;

在上面的語句中,如果使用的是 namespace_name1,那麼在該範圍內 namespace_name2 中的元素也是可用的,如下所示:

#include <iostream>
using namespace std;
 
// 第一個名稱空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
   // 第二個名稱空間
   namespace second_space{
      void func(){
         cout << "Inside second_space" << endl;
      }
   }
}
using namespace first_space::second_space;
int main ()
{
 
   // 呼叫第二個名稱空間中的函式
   func();
   
   return 0;
}

當上面的程式碼被編譯和執行時,它會產生下列結果:

E 引用

《C++ Primer Plus》 by Stephen Prata

C++ 名稱空間 by 菜鳥教程