C++命名空間的具體概括

 

本講基本要求html

     * 掌握:命名空間的做用及定義;如何使用命名空間。
     * 瞭解:使用早期的函數庫 
重點、難點
     ◆命名空間的做用及定義;如何使用命名空間。ios

    在學習本書前面各章時,讀者已經屢次看到在程序中用瞭如下語句:程序員

using namespace std;編程

    這就是使用了命名空間std。在本講中將對它做較詳細的介紹。函數

1、 爲何須要命名空間(問題提出)工具

    命名空間是ANSIC++引入的能夠由用戶命名的做用域,用來處理程序中常見的同名衝突。學習

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

 

/////////若是沒有命名空間的話得C++就是四個做用域??操作系統


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

例:若是在文件中定義了兩個類,在這兩個類中能夠有同名的函數。在引用時,爲了區別,應該加上類名做爲限定:
   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;

////////////////////extern 關鍵字的解釋  參考:  http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html

簡而言之::一是拓展使用C的函數,二是使用全局定義

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

   二、程序中就會出現名字衝突。
   
在簡單的程序設計中,只要人們當心注意,能夠爭取不發生錯誤。可是,一個大型的應用軟件,每每不是由一我的獨立完成的,而是由若干人合做完成的,不一樣的人分別完成不一樣的部分,最後組合成一個完整的程序。假如不一樣的人分別定義了類,放在不一樣的頭文件中,在主文件(包含主函數的文件)須要用這些類時,就用#include命令行將這些頭文件包含進來。因爲各頭文件是由不一樣的人設計的,有可能在不一樣的頭文件中用了相同的名字來命名所定義的類或函數。

例4 名字衝突

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

//例4中的頭文件header1(頭文件1,沒其文件名爲cc8-4-h1.h)
#include <string>
#include <cmath>
using namespace std;
class Student //聲明Student類
   { public:
     Student(int n,string nam,int a)
       { num=n;name=nam;age=a;}
     void get_data();
   private:
     int num;
     string name;
     int age; };
void Student::get_data() //成員函數定義
{ cout<<num<<" "<<name<<" "<<age<<endl; }
double fun(double a,double b)//定義全局函數(即外部函數)
{ return sqrt(a+b);}

在main函數所在的文件中包含頭文件headerl.h:
#include <iostream>
using namespace std;
#include "header1.h" //注意要用雙引號,由於文件通常是放在用用戶目錄中的
int main()
{   Student stud1(101,"Wang",18); //定義類對象studl
   stud1.get_data();
   cout<<fun(5,3)<<endl;
   return 0; }

程序能正常運行,輸出爲
   101 Wang 18
   2.82843

   若是程序員乙寫了頭文件header2.h,在其中除了定義其餘類之外,還定義了類Student和函數fun,但其內容與頭文件headerl.h中的Student和函數fun有所不一樣。

//例4中的頭文件header2
#include <string>
#include <cmath>
using namespace std;
class Student //聲明Student類
{ public:
     Student(int n,string nam,char s) //參數與headerl中的student不一樣
     { num=n;name=nam;sex=s;}
     void get_data();
     private:
     int num;
     string name;
     char sex; };//此項與headerl不一樣

void Student::get_data() //成員函數定義
{ cout<<num<<" "<<name<<" "<<sex<<endl; }

double fun(double a,double b) //定義全局函數 
   { return sqrt(a-b);} //返回值與headerl中的fun函數不一樣
//頭文件2中可能還有其餘內容

   假如主程序員在其程序中要用到headerl.h中的Student和函數fun,於是在程序中包含了頭文件headerl.h,同時要用到頭文件header2.h中的一些內容(但對header2.h中包含與headerl.h中的Student類和fun函數同名而內容不一樣的類和函數並不知情,由於在一個頭文件中每每包含許多不一樣的信息,而使用者每每只關心本身所須要的部分,而不注意其餘內容),於是在程序中又包含了頭文件header2.h。若是主文件(包含主函數的文件)以下:

#include <iostream>
using namespace std;
#include "header1.h"//包含頭文件l
#include "header2.h"//包含頭文件2
int main()
{ Student stud1(101,"Wang",18);
stud1.get_data();
cout<<fun(5,3)<<endl;
return 0; }

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

   三、全局命名空間污染(global namespace pollution)。
   
在程序中還每每須要引用一些庫(包括C++編譯系統提供的庫、由軟件開發商提供的庫或者用戶本身開發的庫),爲此須要包含有關的頭文件。若是在這些庫中包含有與程序的全局實體同名的實體,或者不一樣的庫中有相同的實體名,則在編譯時就會出現名字衝突。

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

   c語言和早期的C++語言沒有提供有效的機制來解決這個問題,沒有使庫的提供者可以創建本身的命名空間的工具。人們但願ANSI C++標準可以解決這個問題,提供—種機制、一種工具,使由庫的設計者命名的全局標識符可以和程序的全局實體名以及其餘庫的全局標識符區別開來。

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

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

如: namespace ns1 //指定命名中間nsl
      
{ int a;
      double b; }

   namespace是定義命名空間所必須寫的關鍵字,nsl是用戶本身指定的命名空間的名字(能夠用任意的合法標識符,這裏用ns1是由於ns是namespace的縮寫,含義請楚),在花括號內是聲明塊,在其中聲明的實體稱爲命名空間成員(namespace member)。如今命名空間成員包括變量a和b,注意a和b仍然是全局變量,僅僅是把它們隱藏在指定的命名空間中而已。若是在程序中要使用變量a和b,必須加上命名空間名和做用域分辨符「::」,如nsl::a,nsl::b。這種用法稱爲命名空間限定(qualified),這些名字(如nsl::a)稱爲被限定名(qualified name)。C++中命名空間的做用相似於操做系統中的目錄和文件的關係,因爲文件不少,不便管理,並且容易重名,因而人們設立若干子目錄,把文件分別放到不一樣的子目錄中,不一樣子目錄中的文件能夠同名。調用文件時應指出文件路徑。

   命名空間的做用是創建一些互相分隔的做用域,把一些全局實體分隔開來。以避免產生老點名叫李相國時,3我的都站起來應答,這就是名字衝突,由於他們沒法辨別老師想叫的是哪個李相國,同名者沒法互相區分。爲了不同名混淆,學校把3個同名的學生分在3個班。這樣,在小班點名叫李相國時,只會有一我的應答。也就是說,在該班的範圍(即班做用域)內名字是唯一的。若是在全校集合時校長點名,須要在全校範圍內找這個學生,就須要考慮做用域問題。若是校長叫李相國,全校學生中又會有3人一齊喊「到」,由於在同一做用域中存在3個同名學生。爲了在全校範圍內區分這3名學生,校長必須在名字前加上班號,如高三甲班的李相國,或高三乙班的李相國,即加上班名限定。這樣就不致產生混淆。

   能夠根據須要設置許多個命名空間,每一個命名空間名錶明一個不一樣的命名空間域,不一樣的命名空間不能同名。這樣,能夠把不一樣的庫中的實體放到不一樣的命名空間中,或者說,用不一樣的命名空間把不一樣的實體隱蔽起來。過去咱們用的全局變量能夠理解爲全局命名空間,獨立於全部有名的命名空間以外,它是不須要用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; //須要指定外層的和內層的命名中間名

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

///花括號後面沒有命名空間

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

有了以上的基礎後,就能夠利用命名空間來解決名字衝突問題。如今,對例4程序進行修改,使之能正確運行。

例5 利用命名空間來解決例4程序名字衝突問題。
修改兩個頭文件,把在頭文件中聲明的類分別放在兩個不一樣的命名空間中。
//例8.5中的頭文件1,文件名爲header1.h

using namespace std;
#include <string>
#include <cmath>
namespace ns1 //聲明命名空間ns1
{ class Student //在命名空間nsl內聲明Student類 
{ public:
Student(int n,string nam,int a)
{ num=n;name=nam;age=a;}
void get_data();
private:
int num;
string name;
int age; };
void Student::get_data() //定義成員函數
   { cout<<num<<" "<<name<<" "<<age<<endl; }

double fun(double a,double b) //在命名空間n引內定義fun函數
{ return sqrt(a+b);}
}

//例8.5中的頭文件2,文件名爲header2.h
#include <string>
#include <cmath>
namespace ns2 //聲明命名空間ns2
{ class Student
{ public:
Student(int n,string nam,char s)
{ num=n;name=nam;sex=s;}
void get_data();
private:
int num;
string name;
char sex; };

void Student::get_data()
{ cout<<num<<" "<<name<<" "<<sex<<endl; }

double fun(double a,double b)
{ return sqrt(a-b);}
}

//main file
#include <iostream>
#include "header1.h" //包含頭文件l
#include "header2.h" //包含頭文件2
int main()
{ ns1::Student stud1(101,"Wang",18);//用命名空間nsl中聲明的Student類定義studt
stud1.get_data(); //不要寫成ns1::studl.get_data(); 
cout<<Ns1::fun(5,3)<<endl; //調用命名空間ns1中的fun函數
ns2::Student stud2(102,"Li",'f'); //用命名空間ns2中聲明的Student類定義stud2
stud2.get_data();
cout<<ns2::fun(5,3)<<endl; //調用命名空間nsl,中的fun函數
return 0; }

解決本題的關鍵是創建了兩個命名空間nsl和ns2,將原來在兩個頭文件中聲叫的類分別放在命名空間nsl和ns2中。注意:在頭文件中,不要把#include命令放在命名空間中,在上一小節的敘述中能夠知道,命名空間中的內容不包括命令行,不然編譯會出錯

分析例4程序出錯的緣由是:在兩個頭文件中有相同的類名Student和相同的函數名fun,在把它們包含在主文件中時,就產生名字衝突,存在重複定義。編譯系統沒法辨別用哪個頭文件中的Student來定義對象studl。如今兩個Student和fun分別放在不一樣的命名空間中,各自有其做用域,互不相干。因爲做用域不相同,不會產:生名字衝突。正如同在兩個不一樣的類中能夠有同名的變量和函數而不會產生衝突同樣。

在定義對象時用ns1::Student(命名空間nsl中的Student)來定義studl,用ns2::Student(命名空間ns2中的Student)來定義stud2。顯然,nsl::Student和ns2::Student是兩個不一樣的類,不會產生混淆。一樣,在調用fun函數時也須要用命名空間名ns]或ns2加以限定。ns1::fun()和ns2::fun()是兩個不一樣的函數。注意:對象studl是用nsl::Student定義的,但對象studl並不在命名空間nsl中。studl的做用域爲main函數範圍內。在調用對象studl的成員函數get_data時,應寫成studl.get_data(),而不該寫成nsl::studl.get_data()。

程序能順利經過編譯,並獲得如下運行結果:
101 Wang l9 (對象studl中的數據)

2.82843 (/5+3的值)
102 Li f (對象studg中的數據)
1.41421 (/5-2的值)

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

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

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

這種方法是有效的,能保證所引用的實體有唯一的名字。可是若是命名空間名字比較長,尤爲在有命名空間嵌套的狀況下,爲引用一個實體,須要寫很長的名字。在一個程序中可能要屢次引用命名空間成員,就會感到很不方便。

1 、使用命名空間別名

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

namespace Television //聲明命名空間,名爲Television
{ ... }

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

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

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

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

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

using nsl::Student;

以上語句聲明:在本做用域(using語句所在的做用域)中會用到命名空間ns1中的成員Student,在本做用域中若是使用該命名空間成員時,沒必要再用命名空間限定。例如在用上面的using聲明後,在其後程序中出現的Student就是隱含地指nsl::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?

產生了二義性,編譯出錯。

三、使用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語句。

5、 無名的命名空間

以上介紹的是有名字的命名空間,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++建議的命名空間的機制,相信使用無名命名空間成員的方法將會取代之前習慣用的對全局變量的靜態聲明。

6、標準命名空間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進行聲明。

7、 使用早期的函數庫

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

若是要用函數庫中的函數,就必須在程序文件中包含有關的頭文件,在不一樣的頭文件中,包含了不一樣的函數的聲明。

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

一、用C語言的傳統方法
頭文件名包括後綴.h,如stdio.h,math.h等。因爲C語言沒有命名空間,頭文件並不存放在命名空間中,所以在C++程序文件中若是用到帶後綴.h的頭文件時,沒必要用命名空間。只需在文件中包含所用的頭文件便可。

#include<math.h>

二、用C++的新方法
  
C++標準要求系統提供的頭文件不包括後綴.h,例如iostream、string。爲了表示與C語言的頭文件有聯繫又有區別,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++的新方法。

轉自:http://hi.baidu.com/rainysky_2006/blog/item/a490e01fc3de7964f724e4d1.html

相關文章
相關標籤/搜索