類範圍與命名空間續
(名字尋找規則)
在版本4中,有關於類範圍中的名字尋找有這麼一段話:
迄今為止,在我們所編寫的程式中,名字尋找(尋找與給定的名字相匹配的聲明的過程)是相對直接的。
(1) 首先,在使用該名字的塊中尋找名字聲明,只需考慮在該項使用之前聲明的名字。
(2) 如果找不到該名字,則在包圍的範圍(外層範圍,也需要在使用之前)中尋找。
(3) C++中所有名字必須在使用之前聲明。
(4) 在名字尋找期間,如果類範圍使用的名字不能確定為成員名,則在包含該類或者成員定義的範圍中尋找,以便找到該名字聲明。
知道理論不見得理解了本質,或者說理解可能會有偏差。下面我們一如既往地做個小測試,測試程式如下:
//findName 標頭檔 class client{public: client(){} client(client& c){} ~client(){} void CallShow() { void show(); show(); //Show(); 函數模板無法單獨聲明,而沒有前項聲 //明是不可以調用的 } private: int id;}; templateclass server{ public: server(){} server(server& s){} ~server(){} void CallShow() { void show(); show(); //Show(); }}; |
//findName.cpp#include<iostream>#include"findName"using namespace std; void show(){ cout<<"Wellcome to call me!"<<endl;} template<class T>void Show(){ cout<<"0 == "<<static_cast(0)<<endl;} int main(){ client cus; cus.CallShow(); server hos; hos.CallShow(); Show(); //注意無參模板函數調用 Show(); Show();} |
#include"findName"
很容易明白,就是把findName檔案的內容直接拷貝放在當前位置,那麼在一個檔案中,我們很容易理解show,Show無法調用的緣故,我們不能在聲明之前使用名字。
如果我們非得把程式調通,也不是沒有辦法,解決方案有兩種方案:
方案一:
#include<iostream>void show(){ std::cout<<"Wellcome to callme!"<<std::endl;}template<class T>void Show(){ std::cout<<"0 =="<<static_cast(0)<<std::endl;}#include"findName"using namespace std; int main(){ client cus; cus.CallShow(); server hos; hos.CallShow(); Show(); Show(); Show();} |
方案二:
#include<iostream>void show();template<class T> //模板無前向聲明方式void Show(){ std::cout<<"0 =="<<static_cast(0)<<std::endl;}#include"findName"using namespace std;void show(){ std::cout<<"Wellcome to callme!"<<std::endl;}int main(){ client cus; cus.CallShow(); server hos; hos.CallShow(); Show(); Show(); Show();} |
上述範圍風格其實是C++的全域範圍,我們可以使用C++的命名空間。
命名空間的名字尋找規則:
(1) 類成員函數優先(按照子類--->基類順序),一經找到停止。
(2) 若沒有,在相應的命名空間中找(如usingnamespace std;就在std中尋找)。
(3) 若還沒有,就根據參數所在命名空間進行尋找。
函數調用匹配原則:(自己理解的)
(1)首先匹配函數名,若能找到進行下一步匹配。
(2)接著匹配函數類型,若無法實現匹配(如參數是泛型),等執行個體化時再匹配。
(3)執行個體化時需要嚴格匹配。
也即一個模板可以在一個函數出現之前調用它,不過調用時的參數必須要有泛型參數,且執行個體化該模板時,被調函數必須在之前出現在相關命名空間。
我們先看一個簡單的例子:
#include<iostream>#include<string> template<class T>void CallShow(){ //Show(); //Show(std::string("empty"));} template<class T>void CallShow(T s){ //show(); Show(s); //Show(std::string("empty"));} template<class T>class display{public: void CallShow(T s) { Show(s); }}; void Show(){ std::cout<<"Empty Show!"<<std::endl;}void Show(std::string s){ std::cout<<"s = "<<s<<std::endl;}int main(){ CallShow(); std::string str = std::string("song"); CallShow(str); display d; d.CallShow(str);} |
由此測試小程式,我們可以得出:
(1) Show(s)調用成功的原因,是由於模板函數的執行個體化時才算是定義
處,而執行個體化必須在調用時發生。
(2) Show() 調用出錯是因為,該處函數沒有泛型參數。所以編譯器可以直接作抉擇,那麼它會在在模板函數執行個體化之前匹配函數。此時Show()並沒有定義,所以調用失敗。
(3) Show(std::string("empty"))調用失敗同(2)。
(4)注意一下CallShow()的調用方式。
當把下面代碼置於namespace std中的話:
void Show(){ std::cout<<"Empty Show!"<<std::endl;} void Show(std::string s){ std::cout<<"s = "<<s<<std::endl;} |
namespace std{void Show(){ std::cout<<"Empty Show!"<<std::endl;} void Show(std::string s){ std::cout<<"s = "<<s<<std::endl;}} |
調用仍然成功,這個不太符合我們的理解,因為在調用Show時,我們並沒有在調用Show時註明名字域std::Show,這貌似不合理。其實這是名字尋找的第三條規則:根據參數的命名空間進行尋找。
在mian函數中寫兩條語句:
Show();
Show(std::string("test"));
不出所料,第一句編譯錯誤:找不到函數名。第二句執行成功。
當把std 改為stdl時,兩句話都編譯不通過。
當上述Show不置於namespace std中,而CallShow,display置於namespace std中時。
namespace std{template<class T>void CallShow(){ //Show(); //Show(std::string("empty"));} template<class T>void CallShow(T s){ //show(); Show(s); //Show(std::string("empty"));} template<class T>class display{ public: void CallShow(T s) { Show(s); }};} |
編譯不通過,錯誤很明顯使用CallShow,display時必須加上範圍限定std::。
如果都加上namespace std那麼執行也會完全通過。
如果代碼改為如下:
#include<iostream>#include<string> namespace std{void Show(int i){}template<class T>void CallShow(){ //Show(); //Show(std::string("empty"));} template<class T>void CallShow(T s){ //show(); Show(s); //Show(std::string("empty"));} template<class T>class display{ public: void CallShow(T s) { Show(s); }};} namespace std{void Show(){ std::cout<<"Empty Show!"<<std::endl;} void Show(std::string s){ std::cout<<"s = "<<s<<std::endl;}} int main(){ std::CallShow(); std::string str = std::string("song"); std::CallShow(str); std::display d; d.CallShow(str); //Show(); Show(std::string("test"));} |
程式能實現正常功能。
但是如果把,下面Show的namespace std去掉的話,那麼就會出現編譯錯誤。此時CallShow裡只會匹配std中的那個Show,這是C++匹配規則。當在該命名空間中找到了就不會去外層範圍中尋找。且不同命名空間之間不會發生重載機制。
本例中,Std中的Show不會與全域的Show形成重載,且當在std中找到Show之後就不會向外層尋找(這其實是c語言中的名字屏蔽原則,局部屏蔽全域,可以用“::”顯示調用)。然後發現參數不匹配所以出錯。
但此處使用“::”不可以,原因不明。
如果去掉std中的Show定義就可以找到全域空間的Show正常運行。
一個小結論:命名空間可以直接使用全域空間中的名字,反之不可(必需用類似std::)。
我們再次改變代碼:
#include<iostream> #include<string> namespace std{void Show(int i){}template<class T>void CallShow(){ //Show(); //Show(std::string("empty"));}template<class T>void CallShow(T s){ //show(); Show(s); //Show(std::string("empty"));}template<class T>class display{public: void CallShow(T s) { Show(s); }};}int main(){ std::CallShow(); std::string str = std::string("song"); std::CallShow(str); std::display d; d.CallShow(str);}namespace std{void Show(){ std::cout<<"Empty Show!"<<std::endl;}void Show(std::string s){ std::cout<<"s = "<<s<<std::endl;}}
|
這段代碼能成功執行煞是費解,難道此處Show不是在執行個體化之後定義的嗎?這個問題留待以後學完模板吧。