C++命名空間

一、 爲什麼需要命名空間(問題提出)

    命名空間是ANSIC++引入的可以由用戶命名的作用域,用來處理程序中 常見的同名衝突。

    C語言中定義了3個層次的作用域,即文件(編譯單元)函數複合語句C++又引入了類作用域,類是出現在文件內的。在不同的作用域中可以定義相同名字的變量,互不於擾,系統能夠區別它們。

 

    1、全局變量的作用域是整個程序,在同一作用域中不應有兩個或多個同名的實體(enuty),包括變量、函數和類等。

例:如果在文件中定義了兩個類,在這兩個類中可以有同名的函數。在引用時,爲了區別,應該加上類名作爲限定:

   class A     //聲明A

    { public

       void funl()//聲明A類中的funl函數

    private

        int i }

   void A::funl() //定義A類中的funl函數

    {…………}

 

    class B //聲明B

    { public

        void funl() //B類中也有funl函數

        void fun2() }

    void B::funl() //定義B類中的funl函數

    { …………}

這樣不會發生混淆。

    在 文件中可以定義全局變量(global variable),它的作用域是整個程序。如果在文件A中定義了一個變量a        int a=3

在文件B中可以再定義一個變量a       int a=5;

在分別對文件A和文件B進行編譯時不會有問題。但是,如果一個程序包括文件A和文件B,那麼在進行連接時,會報告出錯,因爲在同一個程序中有兩個同名的變量,認爲是對變量的重複定義。

   可 以通過extern聲明同一程序中的兩個文件中的同名變量是同一個變量。如果在文件B中有以下聲明:

      extem int a

   表示文件B中的變量a是在其他文件中已定義的變量。由於有此聲明,在程序編譯和連接後,文件A的變量a的作用域擴展到了文件B。如果在文件B中不再對a賦值,則在文件B中用以下語句輸出的是文件A中變量a的值: cout<<a; //得到a的值爲3

 

   2、程序中就會出現名字衝突。

   在簡單的程序設計中,只要人們小心注意,可以爭取不發生錯誤。但是,一個大型的應用軟件,往往不是由一個人獨立完成的,而是由若干人合作完成的,不同的人分別完成不同的部分,最後組合成一個完整的程序。假如不同的人分別定義了類,放在不同的頭文件中,在主文件(包含主函數的文件)需要用這些類時,就用#include命令行將這些頭文件包含進來。由於各頭文件是由不同的人設計的,有可能在不同的頭文件中用了相同的名字來命名所定義的類或函數。

例4 名字衝突

    程序員甲在頭文件headerl.h中定義了類 Student和函數fun()。

// header1.h

......

class Student //聲明Student類

{ 

    ......

}

double fun(...)//定義全局函數(即外部函數)

    ......

}

   程序員乙在頭文件header2h中也定義了類Student和函數fun(),但其內容與頭文件headerlh中的 Student和函數fun有所不同。

// header2.h

......

class Student //聲明Student類,與前面的Student的內容不同。

    ......

};

double fun(...) //定義全局函數,與前面的fun()的內容不同。

   假如主程序員在其程序中要用到headerl.h中的Student和函數fun,因而在程序中包含了頭文件headerl.h,同時要用到頭文件 header2.h中的一些內容(但對header2.h中包含與headerl.h中的Student類和fun函數同名而內容不同的類和函數並不知情),因而在程序中又包含了頭文件 header2.h。如果主文件如下:

#include "header1.h"

#include "header2.h"

int main()

    ......

}

   這時程序編譯就會出錯。因爲在預編譯後,頭文件中的內容取代了對應的#include命令行,這樣就在同一個程序文件中出現了兩個Student類和兩個 fun函數,顯然是重複定義,這就是名字衝突,即在同一個作用域中有兩個或多個同名的實體

   3、全局命名空間污染(global namespace pollution)

   在程序中還往往需要引用一些庫(包括C++編譯系統提供的庫、由軟件開發商提供的庫或者用戶自己開發的庫),爲此需要包含有關的頭文件。如果在這些庫中包含有與程序的全局實體同名的實體,或者不同的庫中有相同的實體名,則在編譯時就會出現名字衝突。

   爲了避免這類問題的出現,人們提出了許多方法,例如:將實體的名字寫得長—些(包含十幾個或幾十個字母和字符);把名字起得特殊一些,包括一些特殊的字符;由編譯系統提供的內部全局標識符都用下劃線作爲前綴,如_complex(),以避免與用戶命名的實體同名;由軟件開發商提供的實體的名字用特定的字符作爲前綴。但是這樣的效果並不理想,而且增加了閱讀程序的難度,可讀性降低了。

   語言和早期的C++語言沒有提供有效的機制來解決這個問題,沒有使庫的提供者能夠建立自己的命名空間的工具。人們希望ANSI C++標準能夠解決這個問題,提供—種機制、一種工具,使由庫的設計者命名的全局標識符能夠和程序的全局實體名以及其他庫的全局標識符區別開來。

二、 什麼是命名空間(解決方案)

   命名空間實際上就是一個由程序設計者命名的內存區域,程序設計者可以根據需要指定一些有名字的空間域,把一些全局實體分別放在各個命名空間中,從而與其他全局實體分隔開來。

如: namespace ns1 //指定命名空間nsl

    

           ......

      }

   namespace 是定義命名空間所必須寫的關鍵字,nsl 是用戶自己指定的命名空間的名字(可 以用任意的合法標識符,這裏用ns1是因爲ns是namespace的縮寫,含義請楚),在花括號內是聲明塊,在其中聲明的實體稱爲命名空間成員(namespace member)。如果在程序中要使用命名空間內定義的實體(類、函數、變量等),必須加上命名空間名和作用域分辨符“::”,如nsl::a,nsl::b。這種用法稱爲命名空間限定(qualified),這些名字(如nsl::a)稱爲被限定名 (qualified name)。C++中命名空間的作用類似於操作系統中的目錄和文件的關係,由於文件很多,不便管理,而且容易重名,於是人們設立若干子目錄,把文件分別放到不同的子目錄中,不同子目錄中的文件可以同名。調用文件時應指出文件路徑。

   命名空間的作用是建立一些互相分隔的作用域,把一些全局實體分隔開來。

   可以根據需要設置許多個命名空間,每個命名空間名代表一個不同的命名空間域,不同的命名空間不能同名。這樣,可以把不同的庫中的實體放到不同的命名空間中,或者說,用不同的命名空間把不同的實體隱蔽起來。過去我們用的全局變量可以理解爲全局命名空間,獨立於所有有名的命名空間之外,它是不需要用 namespace聲明的,實際上是由系統隱式聲明的,存在於每個程序之中。

在聲明一個命名空間時,花括號內不僅可以包括變量,而且還可以包括以下類型:

·變量(可以帶有初始化)

·常量;

·(可以是定義或聲明)

·結構體;

·類;

·模板;

·命名空間(在一個命名空間中又定義一個命名空間,即嵌套的命名空間)

例如

namespace nsl

{ 

    const int RATE=0.08; //常量

   doublepay;       //變量

   doubletax()       //函數

      {return a*RATE}

   namespacens2       //嵌套的命名空間

      {int age}

}

如果想輸出命名空間nsl中成員的數據,可以採用下面的方法:

cout<<nsl::RATE<<endl;

cout<<nsl::pay<<endl;

cout<<nsl::tax()<<endl;

cout<<nsl::ns2::age<<endl; //需要指定外層的和內層的命名中間名

   可以看到命名空間的聲明方法和使用方法與類差不多。但它們之間有一點差別:在聲明類時在右花括號的後面有一分號,而在定義命名空間時,花括號的後面沒有分號。

三、使用命名空間解決名字衝突(使用指南)

有了以上的基礎後,就可以利用命名空間來解決名字衝突問題。

修改兩個頭文件,把在頭文件中聲明的類分別放在兩個不同的命名空間中。

     // header1.h

......

namespace ns1 //聲明命名空間ns1

{

   class Student

   {

      ......

   }

   double fun(...) 

   { 

    ......

   }

}

// header2.h

......

namespace ns2 //聲明命名空間ns2

    class Student

    {

        ...... 

    }

    double fun(double a,double b)

    { 

        ......

    }

}

// main.cpp

#include "header1.h" 

#include "header2.h" 

int main()

{

    ...... 

}

程序能順利通過編譯,並得到以下運行結果:


四、使用命名空間成員的方法

從上面的介紹可以知道,在引用命名空間成員時,要用命名空間名和作用域分辨符對命名空間成員進行限定,以區別不同的命名空間中的同名標識符。即:

命名空間名::命名空間成員名

這種方法是有效的,能保證所引用的實體有惟一的名字。但是如果命名空間名字比較長,尤其在有命名空間嵌套的情況下,爲引用一個實體,需要寫很長的名字。在一個程序中可能要多次引用命名空間成員,就會感到很不方便。

1 、使用命名空間別名

可以爲命名空間起一個別名(namespace alias),用來代替較長的命名空間名。

namespace Television //聲明命名空間,名爲Television

{ ... }

可以用一個較短而易記的別名代替它。如:

namespace TV=Television; //別名TV與原名Television等價

也可以說,別名TV指向原名Television,在原來出現Television的位置都可以無條件地用TV來代替。

2、使用using命名空間成員名

    using後面的命名空間成員名必須是由命名空間限定的名字。例如:

using nsl::Student;

    以上語句聲明:在本作用域(using語句所在的作用域)中會用到命名空間ns1中的成員Student,在本作用域中如果使用該命名空間成員時,不必再用命名空間限定。例如在用上面的using聲明後,在其後程序中出現的Student就是隱含地指nsl::Student,其他命名空間中若有同名的Student,在本作用域中必須用命名空間限定。

    using聲明的有效範圍是從using語句開始到using所在的作用域結束。如果在以上的using語句之後有以下語句:

    Student studl(101,"Wang",18); //此處的Student相當於ns1::Student

    上面的語句相當於

    nsl::Student studl(101,"Wang",18);

    又如:

using nsl::fun; //聲明其後出現的fun是屬於命名空間nsl中的fun

cout<<fun(5,3)<<endl;//此處處的fun函數相當於nsl::fun(5,3)

    顯然,這可以避免在每一次引用命名空間成員時都用命名空間限定,使得引用命名空間成員變得方便易用。

    但是要注意:在同一作用域中用using聲明的不同命名空間的成員中不能有同名的成員。例如:

usmgnsl::Student; //聲明其後出現的Student是命名空間nsl中的Student

usmgns2::Student; //聲 明其後出現的Student是命名空間ns2小的Student

Student stud1; //請問此處的Student是哪個命名中間中的Student?

產生了二義性,編譯出錯

3、使用using namespace命名空間名

    用上面介紹的using命名空間成員名,一次只能聲明一個命名空間成員,如果在一個命名空間中定義了10個實體,就需要使用10次using命名空間成員名。能否在程序中用一個語句就能一次聲明一個命名空間中的全部成員呢?

    C++提供了using namespace語句來實現這一目的。using namespace語句的一般格式爲

using namespace 命名空間名;

例如

using nanlespace nsl;

聲明瞭在本作用域中要用到命名空間nsl中的成員,在使用該命名空間的任何成員時都不必用命名空間限定。如果在作了上面的聲明後有以下語句:

Student studl(101,”Wang”,18); //Student隱含指命名中間nsl中的Student

cout<<fun(5,3)<<endl; //這裏的fun函數是命名中間 nsl中的fun函數

在用usmgnamespace聲明的作用域中,命名空間nsl的成員就好像在全局域聲明的一樣。因此可以不必用命名空間限定。顯然這樣的處理對寫程序比較方便。但是如果同時用usingnamespace聲明多個命名空間時,往往容易出錯。例5中的main函數如果用下面程序段代替,就會出錯。

int main()

{ using namespace nsl;//聲明nsl中的成員在本作用域中可用

using namespace ns2;//聲明ns2中的成員在本作用域中可用

Student studl(101,”Wang",18);

studl.8ct_data();

cout<<fun(5,3)<<endl;

Student stud2(102,"Li",'r');

stud2.get_data();

coutt<<fun(5,3)<<endl;

return O; }

因爲在同一作用域中同時引入了兩個命名空間nsl和ns2,其中有同名的類和函數。在出現Student時,無法判定是哪個命名空間中的 Student,出現二義性,編譯出錯。因此只有在使用命名空間數量很少,以及確保這些命名空間中沒有同名成員時才用using namespace語句。

五、無名的命名空間

以上介紹的是有名字的命名空間,C++還允許使用沒有名字的命名空間,如在文件A中聲明瞭以下的無名命名空間:

namespace //命名空間沒有名字

{ void fun( ) //定 義命名空間成員

{ cout<<"OK."<<endl;}

}

由於命名空間沒有名字,在其他文件中顯然無法引用,它只在本文件的作用域內有效。無名命名空間的成員fun函數的作用域爲文件A(確切地說,是從聲明無名命名空間的位置開始到文件A結束)。在文件A中使用無名命名空間的成員,不必(也無法)用命名空間名限定。

如果 在文件A中有以下語句:

fun();

則執行無名命名空間中的成員fun函數,輸出”OK.”。

在本程序中的其他文件中也無法使用該fun函數,也就是把fun函數的作用域限制在本文件範圍中。可以聯想到:在C浯言中可以用static聲明一個函數,其作用也是使該函數的作用域限於本文件。C++保留了用static聲明函數的用法,同時提供了用無名命名空間來實現這一功能。隨着越來越多的C++ 編譯系統實現了ANSI C++建議的命名空間的機制,相信使用無名命名空間成員的方法將會取代以前習慣用的對全局變量的靜態聲明。

六、標準命名空間std

爲了解決C++標準庫中的標識符與程序中的全局標識符之間以及不同庫中的標識符之間的同名衝突,應該將不同庫的標識符在不同的命名空間中定義(或聲明)。標準C++庫的所有的標識符都是在一個名爲std的命名空間中定義的,或者說標準頭文件(如iostream)中函數、類、對象和類模板是在命名空間 std中定義的。std是standard(標準)的縮寫,表示這是存放標準庫的有關內容的命名空間,含義請楚,不必死記。

這樣,在程序中用到C++標準庫時,需要使用std作爲限定。如

std::cout<<"OK."<<endl; //聲明cout是在 命名空間std中定義的流對象

在有的C++書中可以看到這樣的用法。但是在每個cout,cm以及其他在std中定義的標識符前面都用命名空間std作爲限定,顯然是很不方便的。在大多數的C++程序中常用usmgnamespace語句對命名空間std進行聲明,這樣可以不必對每個命名空間成員一進行處理,在文件的開頭加入以下 using namespace聲明:

using namespace std;

這樣,在std中定義和聲明的所有標識符在本文件中都可以作爲全局量來使用。但是應當絕對保證在程序中不出現與命名空間std的成員同名的標識符,例如在程序中不能再定義一個名爲cout的對象。由於在命名空間std中定義的實體實在太多,有時程序設計人員也弄不請哪些標識符已在命名空間std中定義過,爲減少出錯機會,有的專業人員喜歡用若干個"using命名空間成員”聲明來代替“using namespace命名空間”聲明,如

using Std::string;

using Std::cout;

using Std::cin;

等。爲了減少在每一個程序中都要重複書寫以亡的using聲明,程序開發者往往把編寫應用程序時經常會用到的命名空間std成員的usmg聲明組成一個頭文件,然後在程序中包含此頭文件即可。

如果閱讀了多種介紹C++的書,可能會發現有的書的程序中有using namespace語句,有的則沒有。有的讀者會提出:究竟應該有還是應該沒有?應當說:用標準的C++編程,是應該對命名空間std的成員進行聲明或限定的(可以採取前面介紹過的任一種方法)。但是目前所用的C++庫大多是幾年前開發的,當時並沒有命名空間,庫中的有關內容也沒有放在std命名空間中,因而在程序中不必對std進行聲明。

七、使用早期的函數庫

C語言程序中各種功能基本上都是由函數來實現的,在C語言的發展過程中建立了功能豐富的函數庫,C++從C語言繼承了這份寶貴的財富。在C++程序中可以使用C語言的函數庫。

如果要用函數庫中的函數,就必須在程序文件中包含有關的頭文件,在不同的頭文件中,包含了不同的函數的聲明。

在C++中使用這些 頭文件有兩種方法。

1、用C語言的傳統方法

頭文件名包括後綴.h,如stdio.h,math.h等。由於C語言沒有命名空間,頭文件並不存放在命名空間中,因此在C++程序文件中如果用到帶後綴.h的頭文件時,不必用命名空間。只需在文件中包含所用的頭文件即可。如:

#include<math.h>

2、用C++的新方法

  C++標準要求系統提供的頭文件不包括後綴.h,例如iostreamstring。爲了表示與語言的頭文件有聯繫又有區別,C++所用的頭文件名是在C語言的相應的頭文件名(但不包括後綴.h)之前加一字母c。例如,C語言中有關輸入與輸出的頭文件名爲stdio.h在C++中相應的頭文件名爲cstdio。C語言中的頭文件math.h,在C++中相應的頭文什名爲cmath。C語言中的頭文件 string.h在C++中相應的頭文件名爲cstring。注意在C++中,頭文件cstnng和頭文件strmg不是同一個文件。前者提供C語言中對字符串處理的有關函數(如strcmp,ctrcpy)的聲明,後者提供C++中對字符串處理的新功能。

此外,由於這些函數都是在命名空間std中聲明的,因此在程序中要對命名空間std作聲明。如:

#include<cstdio>

#include<cmath>

using namespace std;

目前所用的大多數C++編譯系統既保留了c的用法,又提供丁C++的新方法。下面兩種用法等價,可以任選。

C傳 統方法 C++新方法

#include<stdio.h> #include<cstdio>

#include<math.h> #include<cmath>

#include<string.h> #include<cstring>

using namespace std;

可以使用傳統的c方法,但應當提倡使用C++的新方法。

發佈了10 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章