c/c++命名空間

C++名字空間/C++命名空間

 0、序言

名字空間是C++提供的一種解決符號名字衝突的方法。

一個命令空間是一個作用域,在不同名字空間中命名相同的符號代表不同的實體。

通常,利用定義名字空間的辦法,可以使模塊劃分更加方便,減少模塊間的相互影響。

 

1、名字空間的成員

定義在名字空間中的實體稱爲名字空間的成員。

名字空間內的名字可以被該名字空間內的其他成員直接訪問,名字空間外的代碼必須指定該名字位於哪個名字空間。

一個名字空間可以包含多種類型的標識符,如下面所列:

變量、常量、函數、結構體/聯合體/枚舉、類、嵌套名字空間

名字空間成員的引用方法如下: 

namespace_name::member_name

  

2、定義名字空間

(1)、一個名字空間可以在兩個地方被定義:在全局範圍層次或者是在另一個名字空間中被定義(這樣就形成一個嵌套名字空間),不能在函數和類的內部定義。

(2)、名字空間可以是不連續的,他是由所有分離定義的部分的總體構成的。一個名字空間可以分散多個文件中,不同的文件中名字空間的定義也是累積的。

     通常將名字空間的聲明放到頭文件中,實現放到源文件中。可以將不相關的成員放到不同的頭文件中。

 (3)、命令空間的作用域不能以分號結尾。

 

3、嵌套名字空間(Nested Namespce)

3.1、普通嵌套名字空間(ordinary nested namespace)

 一個嵌套名字空間就是一個嵌套作用域,其作用域嵌套在包含他的名字空間中。

在外部引用嵌套空間中的成員時,使用下面的形式 

包含嵌套空間的名字空間的名字::嵌套空間的名字::嵌套空間的成員

 下面舉例說明嵌套名字空間定義和使用 

複製代碼

#include <iostream>

namespace MyOutNames
{
    int iVal1 = 100;
    int iVal2 = 200;

    namespace MyInnerNames //定義嵌套名字空間
    {
        int iVal3 = 300;
        int iVal4 = 400;
    }
}

int main(void)
{
    std::cout<<MyOutNames::iVal1<<std::endl;
    std::cout<<MyOutNames::iVal2<<std::endl;
    std::cout<<MyOutNames::MyInnerNames::iVal3<<std::endl;  //使用嵌套名字空間成員
    std::cout<<MyOutNames::MyInnerNames::iVal4<<std::endl;  //使用嵌套名字空間成員

    return 0;
} 

複製代碼

3.2、內聯嵌套名字空間(Inline Namespace C++11)

C++11中,新增inline namespace,指示命名空間中的名稱同時是外層命名空間直接包含的名稱。這便於命名空間的版本管理,減少衝突。

inline描述符使得內聯命名空間中的聲明看起來就好像是直接在外圍的命名空間中進行聲明的一樣。(使用inline關鍵字定義的內聯名字空間成爲默認名字空間。)
inline描述符由命名空間的設計者放置,即命名空間的作者可以通過放置inline描述符來表示當前最新的命名空間是哪個.

複製代碼

// file V98.hpp:
namespace V98
{
    void f(int);    // does something
    // ...
}

// file V99.hpp:
inline namespace V99
{
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file Mine.hpp:
namespace Mine
{
#include "V99.hpp"
#include "V98.hpp"
}

//file example.cpp
#include "Mine.hpp"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);        //default version

複製代碼

 

4、全局名字空間(Global Namespce) 

定義在全局作用域的名字(在任意類、函數或命名空間外部聲明的名字)是定義在全局命名空間中的。
全局命名空間是隱式聲明的,存在於每個程序中。在全局作用域定義實體的每個文件將那些名字加到全局命名空間。
可以用作用域操作符引用全局命名空間的成員。因爲全局命名空間是隱含的,它沒有名字,
所以使用記號如下方法引用全局命名空間的成員。

::member_name

   

5、匿名名字空間(Unnamed Namespace)

 命名空間可以是未命名的,未命名的命名空間在定義時沒有給定名字。其定義方法如下: 

namespace     //No name
{
    //members....
}

未命名的命名空間與其他命名空間不同,未命名的命名空間的定義局部於特定文件,從不跨越多個文本文件。
未命名的命名空間可以在給定文件中不連續,但不能跨越文件,每個文件有自己的未命名的命名空間。
未命名的命名空間用於聲明局部於文件的實體。在未命名的命名空間中定義的變量在程序開始時創建,在程序結束之前一直存在。
未命名的命名空間中定義的名字可直接使用,因爲沒有命名空間名字來限定它們。

複製代碼

#include <iostream>

namespace //unnamed namespace
{
    int count = 1;
}
using namespace std;

namespace //unnamed namespace
{
    void name_printf(void)
    {
        cout << "count = " << count << endl;
    }
}

int main(void)
{
    count = 3;     //直接使用
    name_printf(); //直接使用

    return 0;
}

複製代碼

 未命名的命名空間中定義的名字只在包含該命名空間的文件中可見。

如果另一文件包含一個未命名的命名空間,兩個命名空間不相關,可以定義相同的名字,而這些定義將引用不同的實體。
未命名的命名空間中成員的名字不能與全局作用域中定義的名字相同。例子如下,函數也是同樣的道理。

複製代碼

int i;  // global variable

namespace //unnamed namespace
{
    int i;
}

// error: reference to ‘i’ is ambiguous

複製代碼

 像其他命名空間一樣,未命名的命名空間也可以嵌套在另一命名空間內部。

如果未命名的命名空間是嵌套的,其中的名字按常規方法使用外圍命名空間名字訪問: 

複製代碼

int i; //Global Variable

namespace local 
{
    namespace //unnamed namespace
    {
        int i; // ok: i defined in a nested unnamed namespace is distinct from global i
    }
}

local::i = 42;        

複製代碼

 如果頭文件定義了未命名的命名空間,那麼,在每個包含該頭文件的文件中,該命名空間中的名字將定義不同的局部實體。

未命名的命名空間取代文件中的靜態聲明

在標準 C++ 中引入命名空間之前,程序必須將名字聲明爲static,使它們的作用域約束於一個文件中。
文件中靜態聲明的方法是從 C 語言繼承而來, C++ 不贊成文件靜態聲明,因爲這可能在未來版本中不支持。

所以應該避免文件靜態而使用未命名空間代替。

 

6、名字空間的別名

 可以給名字空間起一個別名,別名是已定義的名字空間的可替換的名稱。

一個命名空間可以有許多別名,所有別名以及原來的命名空間名字都可以互換使用。 

通過下面的形式將別名指定給已定義的名字空間的名字,就可以創建一個名字空間的別名。

namespace 別名 = 已定義的名字空間名字;

 下面舉例說明名字空間別名的定義和使用 

複製代碼

#include <iostream>

namespace MyNames
{
    int iVal1 = 100;
    int iVal2 = 200;
}

namespace MyAlias = MyNames;  //別名定義

int main(void)
{
    std::cout<<MyAlias::iVal1<<std::endl; //別名使用
    std::cout<<MyAlias::iVal2<<std::endl; //別名使用

    return 0;
}

複製代碼

 

7、using聲明 和 using指示 

使用using聲明 和 using指示 的好處就是可以使用使用名字空間中成員時,不必加上名字空間的作用域。

using std::cout; //using聲明

using namespace std; //using指示

7.1、using聲明(using declaration)

一個 using 聲明一次只引入一個命名空間成員。

using 聲明的作用域從 using 聲明點開始,直到包含 using 聲明的作用域的末尾,名字都是可見的。外部作用域中定義的同名實體被屏蔽。

using 聲明可以出現在全局、局部、類的作用域 和 名字空間中。在類作用域中using聲明只能引用基類成員。

複製代碼

//using declaration in Global Scope
#include <iostream>
using std::cout;              //using聲明 
using std::endl;              //using聲明

int main(void)
{
  cout<<"Hello World"<<endl;
  return 0;
}

複製代碼

 

複製代碼

//using declaration in Local Scope
#include <iostream>

void func(void)
{
    using std::cout;
    using std::endl;

    cout << "Using Declarations In Function"<<endl;
}

int main()
{
    func();
//  cout << "Hello" <<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

複製代碼

 

複製代碼

//using declaration in Class Scope
#include <stdio.h>

class B
{
public:
   void f(void) {printf("In B::f()\n");}
   void g(void) {printf("In B::g()\n");}
};

class C
{
public:
   void g() {printf("In C::g()\n");};
};

class D : public B
{
public:
   using B::f;      // OK: B is a base of D2
   using B::g;      // OK: B is a base of D2
// using C::g;      // error: C isn't a base of D2
};

int main(void)
{
   D MyD;
   MyD.f();
   MyD.g();
}

複製代碼

 

複製代碼

//using declaration in Namespce
#include <iostream>

namespace MyNames
{
    using std::string;
    using std::cout;
    using std::endl;

    string str;

    void func(void){cout << "Hello"<<endl;}
}

int main(void)
{
    MyNames::func();
    return 0;
}

複製代碼

 

7.2、using指示(using directive)

using 指示使得整個名字空間中的成員都可見。

using 指示可以出現在全局、局部的作用域 和 名字空間中,不會出現在類的作用域中。

複製代碼

//using directive in Global Scope
#include <iostream>
using namespace std;            //using指示

int main(void)
{
  cout<<"Hello World"<<endl;
  return 0;
}

複製代碼

 

複製代碼

//using directive in Local Scope
#include <iostream>

void func(void)
{
    using namespace std;
    cout << "Using Declarations In Function"<<endl;
}

int main()
{
    func();
//  cout << "Hello" <<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

複製代碼

 

複製代碼

//using declaration in Namespce
#include <iostream>

namespace MyNames
{
    using namespace std;

    string str;
    void func(void){cout << "Hello"<<endl;}
}

int main(void)
{
    MyNames::func();
//    cout<<"Hello"<<endl; //error: ‘cout’ and ‘endl’ were not declared in this scope
    return 0;
}

複製代碼

  

7.3、避免使用using指示

using 指示注入來自一個命名空間的所有名字,這個方法看似簡單,但是如果應用程序使用許多庫,並且用 using 指示使得這些庫中的名字可見,那麼,全局命名空間污染問題就重新出現。

相對於依賴於 using 指示,對程序中使用的每個命名空間名字使用using 聲明更好,這樣做減少注入到命名空間中的名字數目,由 using 聲明引起的二義性錯誤容易發現和修正。

 

8、綜合應用舉例

複製代碼

////file : mynames.hpp
#ifndef MYNAMES__HPP_
#define MYNAMES__HPP_

namespace MyNames
{
    //Member:Variable
    extern int iVal;

    //Member:Class
    class MyString
    {
    public:
        MyString(const std::string&);
        void OutputString(void);
    private:
        std::string str;
    };

    //Member:Function
    void NormalFunc(void);

    //Member:Struct
    struct st_Names
    {
        char ch;
        int count;
    };

    //Member:Union
    union un_Names
    {
        char ch;
        int count;
    };

    //Member:enum
    enum en_Names
    {
        ZERO,
        ONE,
        TWO
    };
}

#endif /* MYNAMES__HPP_ */

//------------------------------------------------------------------------------------------------------------

//file : mynames.cpp
#include <iostream>
#include "mynames.hpp"

namespace MyNames
{
    int iVal = 100;

    MyString::MyString(const std::string& refstr)
    {
        str = refstr;
    }

    void MyString::OutputString(void)
    {
        std::cout << str << std::endl;
    }

    void NormalFunc(void)
    {
        std::cout << "NormalFunc" << std::endl;
    }
}

//-------------------------------------------------------------------------------------------------------------

//file : example.cpp
#include <iostream>
#include "mynames.hpp"

namespace Name = MyNames;

using namespace Name;

int main(void)
{

    std::cout<<iVal<<std::endl;
    std::cout<<Name::iVal<<std::endl;
    std::cout<<MyNames::iVal<<std::endl;

    MyNames::MyString mystr("Hello");
    mystr.OutputString();
    MyNames::NormalFunc();

    MyNames::st_Names myst;
    myst.count = 0;

    MyNames::en_Names myen;
    myen = MyNames::ZERO;

    MyNames::un_Names myun;
    myun.count = 1;

    return 0;
}

 

 

 

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

命名空間是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<

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

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

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

有了以上的基礎後,就可以利用命名空間來解決名字衝突問題。現在,對例4程序進行修改,使之能正確運行。 
例5 利用命名空間來解決例4程序名字衝突問題。 
修改兩個頭文件,把在頭文件中聲明的類分別放在兩個不同的命名空間中。 
//例8.5中的頭文件1,文件名爲header1.h 
using namespace std; 
#include 
#include 
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<

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

從上面的介紹可以知道,在引用命名空間成員時,要用命名空間名和作用域分辨符對命名空間成員進行限定,以區別不同的命名空間中的同名標識符。即: 
命名空間名::命名空間成員名 
這種方法是有效的,能保證所引用的實體有惟一的名字。但是如果命名空間名字比較長,尤其在有命名空間嵌套的情況下,爲引用一個實體,需要寫很長的名字。在一個程序中可能要多次引用命名空間成員,就會感到很不方便。 
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。 
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<

五、 無名的命名空間

以上介紹的是有名字的命名空間,C++還允許使用沒有名字的命名空間,如在文件A中聲明瞭以下的無名命名空間: 
namespace //命名空間沒有名字 
{ void fun( ) //定 義命名空間成員 
{ cout<<”OK.”<

六、標準命名空間std

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

七、 使用早期的函數庫

C語言程序中各種功能基本上都是由函數來實現的,在C語言的發展過程中建立了功能豐富的函數庫,C++從C語言繼承了這份寶貴的財富。在C++程序中可以使用C語言的函數庫。 
如果要用函數庫中的函數,就必須在程序文件中包含有關的頭文件,在不同的頭文件中,包含了不同的函數的聲明。 
在C++中使用這些 頭文件有兩種方法。 
1、用C語言的傳統方法 
頭文件名包括後綴.h,如stdio.h,math.h等。由於C語言沒有命名空間,頭文件並不存放在命名空間中,因此在C++程序文件中如果用到帶後綴.h的頭文件時,不必用命名空間。只需在文件中包含所用的頭文件即可。如 
#include 
2、用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 
#include 
using namespace std; 
目前所用的大多數C++編譯系統既保留了c的用法,又提供丁C++的新方法。下面兩種用法等價,可以任選。 
C傳 統方法 C++新方法 
#include #include 
#include #include 
#include #include 
using namespace std; 
可以使用傳統的c方法,但應當提倡使用C++的新方法。

 


C++另外有一種匿名的命名空間,來保證生成的符號是局部的,這樣對於匿名空間中的變量等,外部都是不可見的.

//test3.cpp

static void bar(){}

namespace //匿名的命名空間
{
    float bar2;
    int foo;
}

//test4.cpp
extern int foo;
extern void bar();
extern float bar2; 
int main()
{
bar();                    //外部的bar()被聲明爲static,這裏鏈接不到符號.不能訪問
bar2 = 0.1f;          //外部的匿名空間哩,這裏也不能訪問.
foo = 0xFF;
return 0;
};//如果將test4的目標和test3的目標進行鏈接,實際上是找不到這些符號的.鏈接會失敗.

匿名的命名空間是C++的特性,相對於C的static聲明來說,可以在匿名的空間裏面聲明很多變量和函數,這樣可以省去了對每個變量和函數添加static聲明.
實質上匿名空間的功能跟static聲明是一樣的.

複製代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章