第18章 用於大型程序的工具 18.2命名空間

18.2 命名空間

命名空間可以有效的防止變量名重複,命名空間就是一個作用域,可以在命名空間中定義命名空間,但是不能在類和函數中定義。

18.2.1 命名空間定義

使用這種方法來定義命名空間

namespace xxx{
}

命名空間都是一個作用域,所以和作用域中的變量是一樣的,變量名字不能重複。

和作用域不一樣的是,命名空間可以不是連續的。我們定義一個命名空間,如果之前沒有這個空間,則創建這個空間,如果之前就已經有了這個空間,那麼就是在這個空間中添加新的成員。

對於頭文件和cpp文件,我們可以分別將其寫在命名空間中
在這裏插入圖片描述
頭文件通常不包含在命名空間中,因爲這樣做會導致頭文件擁有的成員也在該空間中。

全局命名空間通過
::來訪問

命名空間是可以嵌套的,但是外層的命名無法直接訪問內層命名空間定義的成員,使用inline命名空間可以解決這個問題

namespace B{
inline namespace A{
}
}

命名空間B可以直接訪問命名空間A中的變量了。

我們可以不爲命名空間命名,這樣的空間叫做
未命名的命名空間,在未命名的命名空間中聲明的變量都是靜態類型,可以直接訪問,因爲它沒有名字,所以我們無法通過類作用域符來訪問。 但是其作用域在當前的文件中,無法跨多個文件,這點和全局變量不一樣。

因爲它可以直接訪問,所以這就要求全局變量的名字和未命名空間的名字不能一樣,否則會有二義性。

修飾爲static的全局變量作用域爲當前的文件,無法通過extern在其他文件中訪問。

18.2.2 使用命名空間

我們可以通過namespace::變量名來訪問變量,但是這樣比較繁瑣,尤其是變量名字很長的時候。

我們可以爲命名空間起別名

namespace c_p_p = C_plus_plus;
namespace A = BB::CC::AA;

這對於嵌套的命名空間也是適用的。

using聲明

我們也可以適用using來引入命名空間的一個成員,using聲明可以適用在任何作用域中。包含全局作用域,局部作用域,命名空間作用域和類作用域等中。。。

using指示

using指示使用

using namespace std;

的方式,將命名空間中的所有成員都引入當前的作用域,因爲這麼做我們無法確定引入了什麼成員,所以這麼做是由風險的。
同時using指示只能用在全局,局部、命名空間作用域中。

using指示會導致命名空間的左右成員對於使用的地方都是全部可見的,如果有多個命名空間使用了using指示,可能會導致多個命名空間之間由相同名字的變量,從而導致衝突。

另一個問題是,可能當前作用域中定義的變量和命名空間的成員名字有衝突,導致報錯。

所以使用using指示是存在一定風險的,但是如果寫一般的小代碼,不是大型的項目的話,使用一下還是很方便的。

18.2.3 類、命名空間和作用域

在命名空間中名字查找是從內開始向外找的,同時名字查找只從其聲明語句之前的語句中查找,

namespace A{
	int a;
	int b;
	class cls_A{
	public:
	void f(){
	++a;//正確
};
void f1(){
++c;//錯誤
}
	}
	int c;
}

在命名空間中的類,在使用名字的時候,首先從當前函數的作用域中查找,然後從類的數據成員中找,然後從基類的數據成員中找,然後再從外部命名空間找,逐層向外的方式。

有一個例外,在我們使用

stirng s;
cout<<s;

的時候,對於<<是重載在string中的,但是如果不顯式的引入該成員,上面的操作依舊是對的。

當我們給函數傳遞一個類類型的對象時,除了在常規的作用域查找之外還會查找實參類所屬的命名空間。這對傳遞類的引用或者指針的調用同樣有效。

對於<<,其形參是cout和string類型,所以編譯器會在ostream和stirng的命名空間中查找對應的<<操作。此operator<<(std::cout,string s)定義在std中,找得到所以會直接調用。

對於友元函數,之前學習到,如果一個類中聲明瞭一個友元函數,但是在類外面沒有該函數的聲明,那麼這個友元函數是不可見的。

但是如果在命名空間中,在命名空間的類名中定義了友元,但是沒有任何聲明。編譯器會認爲這些友元函數是它最近的外層命名空間的成員。 儘管我們並沒有對此做任何聲明和定義。

在這裏插入圖片描述
書上的例子就是一個很好的例子,定義了cobj。f(cobj),此時A命名空間是沒有在當前作用域中的,但是f(cobj)調用成功了,因爲他會在實參的命名空間中查找這個函數是否存在,答案是存在的,所以調用成功,但是對於f2(),因爲其沒有形參,無法進行推斷,所以是錯誤的。

我覺得這種方法反而讓C++變得更加複雜化了。

練習
18.18

如果是string,那麼調用的是string的命名空間std中定義的swap版本,而如果是int則使用的默認版本,如果我們定義了自己版本的string或者int類的swap,那麼調用的就是自己定義的swap。

18.19

如果使用的是std::swap()那麼調用的一致就是std標準庫的版本。

18.2.4 重載和命名空間

之前學習到,一可以通過函數的實參推算出具體調用哪一個函數。以及在該實參類型的命名空間中尋找這個函數,其實這就以及構成了重載。

就和swap一樣,首先使用using std::swap();然後定義自己的版本,我們調用了swap(),就可以根據實參類型來推斷。

重載和using聲明

當我們使用using namespace::func;的時候,在該命名空間下的所有func都對於當前作用域是可見的,所以和當前作用域的同名函數構成了重載的關係,因此在當前作用域中調用func的時候,所以的func都是候選函數。

但是如果命名空間和當前作用域的函數有一個一摸一樣的時候,則編譯器會報錯。。
但是使用using指示是不會的,就算所有參數都一樣,using指示依舊不會報錯

所以使用using聲明,看起來更好。

跨越多個using指示的重載

其實這個和上面的是一樣的,只要是同名函數就會被選入候選列表。

練習
18.20

所有compute函數都是候選函數,
其中
compute(const void*);
compute(int);
compute(double,double=3.4);
compute(char*,char*=0);
是可行函數,但是除了int,其餘的函數都需要類型轉換,所以comput(int)是最終匹配的函數。

如果using放在main函數中,那麼外面的compute函數都會被覆蓋,此時mian函數中可見的函數時

compute();

compute(const void*);

但是compute()不是可行的,compute(const void*);是可行的,所以直接調用的是compute(const void*);

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