天天看點

C++ Primer筆記(十六)命名空間

1、在一個給定的作用域中定義的每個名字,在該作用域中必須是唯一的。

對龐大、複雜的應用程式而言,這個很難滿足。由獨立開發的庫構成的複雜程式更有可能遇到名字沖突,因為庫傾向于使用全局名字:模闆名、類型名或函數名。

命名沖突問題被稱為命名空間污染。

2、命名空間為防止名字沖突,提供了更加可控的機制。命名空間能夠劃分全局命名空間。一個命名空間是一個作用域,通過在命名空間内定義庫中的名字,就可以避免全局名字固有的限制。

命名空間以namespace開始,後接命名空間的名字。如:

namespace aa

{

 class Bar

{

 public:

  Bar(){}

};

class Bar2{};

}

該命名空間定義了兩個類。

3、在定義命名空間的作用域中,命名空間的名字必須是唯一的。命名空間可在全局作用域或其他作用域内部定義,但不能在函數或類内定義。

注意:命名空間不能以分号結束。

4、每個命名空間是一個作用域,在同一個命名空間中,每個名字也同樣必須是唯一的。每個名字可以在其他的命名空間中引用,但必須指出名字所屬的名字空間。

如:aa::Bar bar;

每次在使用某一命名空間的成員,都指定命名空間名非常麻煩。可以像std中定義的命名空間那樣,使用using聲明,來獲得對我們經常使用的名字直接通路。

如:using std::cout;

using aa::Bar;

5、同一命名空間可以在幾個部分定義,命名空間由各個部分的總和構成。一個命名空間的分離部分甚至可以分散在多個檔案中。

namespace  namespace_name

{

}

該定義既可以定義新的命名空間,也可以添加到現存命名空間。

6、命名空間可以不連續意味着:可以用分離的接口檔案和實作檔案構成命名空間。

1)定義類的命名空間成員,以及作為類接口一部分的函數聲明與對象聲明,可以放在頭檔案中。使用命名空間成員的檔案可以包含這些檔案。

2)命名空間成員的定義可以放在單獨的源檔案中。如:

//namespace_a.h

namespace  namespace_a

{

  class Bar

{

public:

 Bar(){}

 void something();

};

}

//namespace_a.cpp

#includer“namespace_a.h”

namespace namespace_a

{

 void Bar::dosomething

{

   //

}

void func();

}

7、在命名空間内部定義的函數,可以使用同一命名空間中定義的名字的簡寫形式。如:

namespace A

{

  Bar bar;//無需寫成namespace_a::Bar bar;

}

上例中的void func()也可以在命名空間的外部定義,但是必須指定該名字所屬的命名空間。如:

void A::func()

{

}

雖然可以在命名空間定義的外部定義命名空間,但隻有包圍成員聲明的命名空間,可以包含成員的定義。

如dosomething函數可以在定義在A作用域中,也可以定義在全局作用域中,但不能定義在不相關的命名空間中。

經過實踐證明上述Bar類的成員函數dosomething也可以在名字空間之外定義。

要寫成void A::Bar::dosomething(){};

8、全局命名空間是隐式聲明的,沒有名字。包括定義在全局作用域的名字,存在每個程式中。在全局作用域定義的每個檔案中的名字将被加入到全局名字空間。

可以用作用域操作符引用全局命名空間的成員。如:::member_name

9、可以在其他命名空間定義新的命名空間,這成為命名空間的嵌套。

嵌套時,外圍命名空間中聲明的名字被嵌套命名空間中同一個名字的聲明所屏蔽。外圍命名空間之外的代碼隻能通過限定名來引用。如

int g;

namespace A

{

namespace B

{

 int a;

}

namespace C

{

B::a=20;

::g=39;

}

}

嵌套命名空間的成員的名字,由外圍命名空間的名字和嵌套命名空間的名字構成。

如需要引用B中的成員則應為:A::B::a;

10、使用namespace定義命名空間時,也可以不指定名字。此時命名空間是未命名的。

1)未命名的空間局部于特定的檔案,不跨越多個文本檔案。同一個未命名空間的定義也可以是不連續的。未命名的空間用于聲明局部于檔案的實體,它内的成員在程式開始時建立,在程式結束前一直存在。

2)因為沒有空間名,是以未命名空間的成員可以直接使用。如果頭檔案定義了未命名的名字空間,那麼,在每個包含該頭檔案的檔案中,該命名空間的名字将定義不同的局部實體。

3)未命名空間中定義的名字可以在該命名空間所在的作用域找到,也就是說未命名空間好像不存在似的。可以假定所有命名空間的内部都是有未命名的空間組成。如全局命名空間的未命名空間,在全局空間和其内的未命名空間名字不能相同,也就很好了解了。

11、除了在函數或其他作用域内部,不應該在頭檔案中使用using聲明或using訓示。

頭檔案應該隻定義作為其接口的一部分的名字。一個using聲明一次隻引入一個命名空間成員。

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

可以使用命名空間别名,将較短的名字與命名空間的名字關聯。如

namespace cplusplus_primer;

namespace cpp=cplusplus_primer;

12、using聲明以關鍵字using namespace開頭,後接命名空間的名字。

它将名字直接放入出現using聲明的作用域。而using訓示将命名空間成員提升到包含命名空間本身和using訓示的最近作用域的效果。

如namespace a

{

  int i,j;

}

void f()

   {

 using namespace a;//将i,j提升到包含namespace a和f的全局空間中。

cout<<i*j<<endl;

}

此種提升規則可能會導緻命名空間的名字會與外圍作用域中定義的其他名字沖突。

如果在f外的全局作用域定義int j ;此時的using namespace a;将a的成員提升到全局作用域,在f内使用j就會出現歧義的情況。

這種沖突是允許的。但是為了使用j必須指明想使用哪個j。使用::j引用全局作用域的變量,使用a::j引用a中的變量。

13、using訓示會注入另一個命名空間的所有名字,如果程式使用很多庫,并且使用using訓示使得這些庫中的名字可見,那麼全局名字空間污染的問題就會重新出現。使用using聲明是不錯的方法。

std::string s;

getline(std::cin,s);

為什麼無需使用std::getline就可以使用該函數?

它給出了屏蔽命名空間名字規則的一個重要例外:與類本身定義在同一命名空間的函數,在用類類型對象作為實參時是可見的。

當編譯器看到getline(std::cin,s)時,它會在目前作用域、包含調用的作用域以及定義cin的類型和string類型的命名空間中查找比對的函數。

如namespace A

{

 class Bar

{

    public:

     Bar(){};

};

void print(Bar &b){};

}

void func()

{

  A::Bar bar;

  print(bar);//調用A中的print.因為它與Bar類處于同一命名空間。

}

14、原來在介紹友元聲明時提到,友元可以在類内聲明時定義。在類内定義的友元與類具有相同的作用域,是以在類内定義的友元也适用于上述例外的情況。

15、在同一個作用域的函數才有重載。不同作用組僅僅存在函數名屏蔽。

16、在同一命名空間内,命名空間對函數比對有兩個影響:

1)using聲明或using訓示可以将函數加到候選集合中。

using聲明聲明一個名字,就像在模闆那一章介紹的一樣,沒有辦法用using聲明來引用特定函數的聲明。

如:using NS::print(int);//錯誤。必須寫成:using NS::print;

如果命名空間内部的print是重載的,則該函數名字的using聲明,聲明了所有具有該名字的函數。它們被加入了目前作用域,都在目前作用域中可見。

如果使用using聲明時,該作用于已存在相同函數名且形參表相同的函數,則會出錯。如果函數名相同但形參表不同,那麼聲明過來的重載函數會與原來的函數構成新的更大的重載集合。

2)using訓示将命名空間的成員提升到外圍作用域。如果命名空間函數與命名空間所在的作用域中聲明的函數同名,就将命名空間成員加到重載集合中。

繼續閱讀