1. 本地名稱和全局名稱
我們都知道在諸如這般的代碼中:
// global變量
int x;
void someFunc(){
// local變量
double x;
// 讀一個(gè)新值賦予local變量x
std::cin >> x;
}
這個(gè)讀取數(shù)據(jù)的語(yǔ)句指涉的是local變量x音诈,而不是global變量x幻碱,
因?yàn)閮?nèi)層作用域的名稱會(huì)遮掩(遮蔽)外圍作用域的名稱绎狭。
當(dāng)編譯器處于someFunc的作用域內(nèi)并遭遇名稱x時(shí),
它在local作用域內(nèi)查找是否有什么東西帶著這個(gè)名稱褥傍,
如果找到就不再找其他作用域儡嘶。
本例的someFunc的x是double類型而global x是int類型,但那不要緊恍风。
C++的名稱遮掩規(guī)則(name-hiding rule)所做的唯一事情就是蹦狂,遮掩名稱。
至于名稱是否應(yīng)和相同或不同的類型朋贬,并不重要凯楔。
本例中一個(gè)名為x的double遮掩了一個(gè)名為x的int。
2. 子類中的名稱和父類中的名稱
現(xiàn)在導(dǎo)入繼承兄世,我們知道啼辣,當(dāng)位于一個(gè)derived class成員函數(shù)內(nèi)指涉(refer to) base class內(nèi)的某物(也許是個(gè)成員函數(shù)啊研,typedef御滩,或成員變量)時(shí),
編譯器可以找出我們所指涉的東西党远,因?yàn)閐ervied class繼承了聲明于base class內(nèi)的所有東西削解。
實(shí)際運(yùn)作方式是,derived class作用域被嵌套在base class作用域內(nèi)沟娱。
class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf2();
void mf3();
...
};
class Dervied: public Base{
public:
virtual void mf1();
void mf4();
...
};
此例內(nèi)含一組混合了public和private名稱氛驮,以及一組成員變量和成員函數(shù)名稱。
這些成員函數(shù)包括pure virtual济似,impure virtual和non-virtual三種矫废,
這是為了強(qiáng)調(diào)我們談的是名稱,和其他無(wú)關(guān)砰蠢。
這個(gè)例子也可以加入各種名稱類型蓖扑,例如enum,nested class和typedef台舱。
整個(gè)討論中唯一重要的是這些東西的名稱律杠,至于這些東西是什么并不重要。
本例使用單一繼承竞惋,然而一旦了解單一繼承下發(fā)生的事柜去,很容易就可以推想C++在多重繼承下的行為。
假設(shè)dervied class內(nèi)的mf4的實(shí)現(xiàn)碼部分像這樣:
void Derived::mf4(){
...
mf2();
...
}
當(dāng)編譯器看到這里使用名稱mf2拆宛,必須估算它指涉(refer to)什么東西嗓奢。
編譯器的做法是查找各作用域,看看有沒(méi)有某個(gè)名為mf2的聲明式浑厚。
首先查找local作用域(也就是mf4覆蓋的作用域)蔓罚,在那沒(méi)找到任何東西名為mf2椿肩,
于是查找其外圍作用域,也就是class Derived覆蓋的作用域豺谈,還是沒(méi)找到任何東西名為mf2郑象,于是再往外圍移動(dòng),本例為Base class茬末。
在那兒編譯器找到一個(gè)名為mf2的東西了厂榛,于是停止查找。
如果Base內(nèi)還是沒(méi)有mf2丽惭,查找動(dòng)作便繼續(xù)下去击奶,
首先找內(nèi)含Base的那個(gè)namespace的作用域(如果有的話),最后往global作用域找去责掏。
3. 遮掩父類名稱
再次考慮前一個(gè)例子柜砾,這次讓我們重載mf1和mf3,并且添加一個(gè)新版mf3到Dervied去换衬。
Derived重載了mf3痰驱,那是一個(gè)繼承而來(lái)的non-virtual函數(shù),
這會(huì)使整個(gè)設(shè)計(jì)立刻顯得疑云重重瞳浦。
但為了充分認(rèn)識(shí)繼承體系內(nèi)的“名稱可視性”担映,我們暫時(shí)安之若素。
class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived: public Base{
public:
virtual void mf1();
void mf3();
void mf4();
...
};
這段代碼的行為會(huì)讓每一位第一次面對(duì)它的C++程序員大吃一驚叫潦。
以作用域?yàn)榛A(chǔ)的“名稱遮掩規(guī)則”并沒(méi)有改變蝇完,因此base class內(nèi)所有名為mf1和mf3的函數(shù)都被derived class內(nèi)的mf1和mf3函數(shù)遮掩掉了。
從名稱查找觀點(diǎn)來(lái)看矗蕊,Base::mf1
和Base::mf3
不再被Derived繼承短蜕。
Derived d;
int x;
...
// 沒(méi)問(wèn)題,調(diào)用Derived::mf1
d.mf1();
// 錯(cuò)誤傻咖,因?yàn)镈erived::mf1遮掩了Base::mf1
d.mf1(x);
// 沒(méi)問(wèn)題朋魔,調(diào)用Base::mf2
d.mf2();
// 沒(méi)問(wèn)題,調(diào)用Derived::mf3
d.mf3();
// 錯(cuò)誤没龙,因?yàn)镈erived::mf3遮掩了Base::mf3
d.mf3(x);
如你所見(jiàn)铺厨,上述規(guī)則都適用,即使base class和derived class內(nèi)的函數(shù)有不同的參數(shù)類型也適用硬纤,
而且不論函數(shù)是virtual或non-virtual一體適用解滓。
這和上文一開(kāi)始展示的道理相同,當(dāng)時(shí)函數(shù)someFunc內(nèi)的double x遮掩了global作用域內(nèi)的int x筝家,
如今Derived內(nèi)的函數(shù)mf3遮掩了一個(gè)名為mf3但類型不同的Base函數(shù)洼裤。
4. using聲明
不幸的是,你通常會(huì)想繼承重載函數(shù)溪王,
實(shí)際上腮鞍,如果你正在使用public繼承而又不繼承那些重載函數(shù)值骇,就是違反了base和derived class之間的is-a關(guān)系,
而is-a關(guān)系是public繼承的基石移国。
因此吱瘩,你幾乎總會(huì)想要推翻(override)C++對(duì)“繼承而來(lái)的名稱”的缺省遮掩行為。
你可以使用using聲明式達(dá)成目標(biāo):
class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
...
};
class Derived: public Base{
public:
// 讓Base class內(nèi)名為mf1和mf3的所有東西迹缀,
// 在Derived作用域內(nèi)都可見(jiàn)(并且public)
using Base::mf1;
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
...
};
現(xiàn)在繼承機(jī)制一如往昔的運(yùn)作:
Derived d;
int x;
...
// 仍然沒(méi)問(wèn)題使碾,仍然調(diào)用Derived::mf1
d.mf1();
// 現(xiàn)在沒(méi)問(wèn)題了,調(diào)用Base::mf1
d.mf1(x);
// 仍然沒(méi)問(wèn)題祝懂,仍然調(diào)用Base::mf2
d.mf2();
// 沒(méi)問(wèn)題票摇,調(diào)用Derived::mf3
d.mf3();
// 現(xiàn)在沒(méi)問(wèn)題了,調(diào)用Base::mf3
d.mf3(x);
這意味如果你繼承base class并加上重載函數(shù)砚蓬,而你又希望重新定義或覆寫(xiě)(推翻)其中一部分矢门,
那么你必須為那些原本會(huì)被遮掩的每個(gè)名稱引入一個(gè)using聲明式,否則某些你希望繼承的名稱會(huì)被遮掩灰蛙。