定義區分
- 在派生-對象中:優先考慮隱藏,此時派生類中的覆蓋(重寫)也是隱藏;沒有隱藏的情況下,子類對象才能調用父類重載函數。[此時感覺virtual沒用,]
- 在派生-指針或者引用中:只用覆蓋(重寫)和重載;
注:C++ Primmer P550解釋為:D1的fnc并沒有覆蓋(重寫)Base的虛函數fnc…實際上,D1…此時擁有了兩個名為fnc的函數:一個是D1從Base繼承而來的(隱藏的)虛函數fnc;另一個是D1自己定義的接受的接受int參數的非虛函數fnc。
其他有利于理解的說法:如果派生類定義了一個與基類成員函數同名的函數(無論參數列表是否相同),那么派生類中的這個函數將隱藏基類中的所有同名函數(隱藏僅僅指類的對象,對于指向派生類的基類指針仍然可以調用其他虛函數)
下面是我寫的兩個函數示例(后續優化)
/*
* 知識點:隱藏、覆蓋(重寫)、重載
* One:對于隱藏、覆蓋(重寫)
* 在派生-對象中:優先考慮隱藏,此時派生類中的覆蓋(重寫)也是隱藏;沒有隱藏的情況下,子類對象才能調用父類重載函數。[此時感覺virtual沒用,]
* 在派生-指針或者引用中:只用覆蓋(重寫)和重載;
*
*
其他有利于理解的說法
* 如果派生類定義了一個與基類成員函數同名的函數(無論參數列表是否相同),
* 那么派生類中的這個函數將隱藏基類中的所有同名函數(隱藏僅僅指類的對象,
* 對于指向派生類的基類指針仍然可以調用其他虛函數)
*/#include <iostream>
using namespace std;class Base
{
public:Base(int a) : m_v(a) {}virtual void setM() { m_v = 0; }virtual void setM(int a) { m_v = a; }virtual int getM() { return m_v; }
protected:int m_v;
};class Drive : public Base
{
public:Drive() : Base(42) {}void setM() override { m_v = 1000; } // 使用 override 關鍵字明確這是一個重寫的方法
};class DDrive : public Drive
{
public:DDrive() : Drive() {}
};int main()
{Base b(6);cout << "Base: " << b.getM() << endl; // 輸出 6b.setM();cout << "Base setM() default(0): " << b.getM() << endl; // 輸出 0,因為調用了 Base 的 setM()b.setM(9);cout << "Base setM(9): " << b.getM() << endl; // 輸出 9cout << endl;Drive d;cout << "Drive Base default(): " << d.getM() << endl; // 輸出 42,因為 Drive 的構造函數調用了 Base(42)d.setM();cout << "Drive setM() default(0): : " << d.getM() << endl; // 輸出 1000,因為調用了 Drive 的 setM()//d.setM(1001); // Error:函數調用參數過多;派生類對象對父類進行隱藏//cout << "Drive (1001) : " << d.getM() << endl; // 輸出 1000,因為調用了 Drive 的 setM()// 以下部分展示了多態性Base* pb = &b;cout << "Base: " << pb->getM() << endl; // 輸出 9pb->setM();cout << "Base: " << pb->getM() << endl; // 輸出 0,因為 pb 指向 Base 對象,調用了 Base 的 setM()pb->setM(9);cout << "Base: " << pb->getM() << endl; // 輸出 9Base* pd = &d;cout << "Drive: " << pd->getM() << endl; // 輸出 1000pd->setM();cout << "Drive: " << pd->getM() << endl; // 輸出 1000,因為 pd 指向 Drive 對象,調用了 Drive 的 setM()pd->setM(1001);cout << "Drive: " << pd->getM() << endl; // 輸出 1001,因為調用了 Drive 的 setM(int)// 延申:子類的不恰當隱藏會影響到孫類"!!!對象!!!",但并不影響孫類指針DDrive dd;cout << "Drive Base default(): " << dd.getM() << endl;dd.setM();cout << "Drive setM() default(0): : " << dd.getM() << endl;//dd.setM(1001); // Error:函數調用參數過多;子類的不恰當隱藏會影響到孫類!!!對象!!!cout << "Drive (1001) : " << dd.getM() << endl;Base* pdd = ⅆcout << "Drive: " << pdd->getM() << endl;pdd->setM();cout << "Drive: " << pdd->getM() << endl;pdd->setM(1001);cout << "Drive: " << pdd->getM() << endl;return 0;
}
還有個更長的示例
#include <iostream>
using namespace std;/*
*一、
*在C++中,重寫(Override)和隱藏(Hiding)是兩個不同的概念,它們在某些情況下可能會產生沖突或混淆,尤其是在涉及繼承時。
但是,這兩個概念本身并不直接沖突,而是需要開發者明確理解和區分它們以避免潛在的問題。- 重寫(Override)
重寫發生在子類中,當子類提供了一個與基類中的虛函數具有相同簽名(即相同的函數名、返回類型、參數列表)的成員函數時。
這允許子類改變從基類繼承的虛函數的行為。重寫是動態綁定的一部分,因此當通過基類指針或引用調用重寫的虛函數時,將調用子類中的版本(如果指針或引用實際上指向子類對象)。- 隱藏(Hiding)
隱藏也發生在子類中,但它通常與名稱沖突相關。當子類提供了一個與基類中的非虛函數具有相同名稱的成員函數時,基類的該函數在子類中將被隱藏。
這意味著在子類的**對象**直接調用該函數時,將調用子類中的版本,而不是基類中的版本。
但是,如果通過基類指針或引用調用該函數(即使該指針或引用實際上指向子類對象),仍然會調用基類中的版本(除非基類中的函數也是虛函數)。*沖突和混淆
- 重寫和隱藏之間的主要混淆在于它們都與函數名和繼承有關,但行為卻截然不同。如果開發者不清楚何時發生重寫、何時發生隱藏,可能會導致意外的行為。
- 此外,如果基類中的函數被設計為虛函數(以支持多態),但子類中的同名函數沒有被聲明為override(在C++11及更高版本中支持),并且基類中的函數簽名在子類中發生了更改(例如參數數量或類型不同),
那么子類中的函數實際上將隱藏基類中的虛函數,而不是重寫它。
這可能導致代碼中的錯誤,因為開發者可能期望實現多態行為,但實際上卻得到了隱藏行為。為了避免這種混淆,建議:
- 在子類中重寫基類的虛函數時,使用override關鍵字(如果編譯器支持)。這將使編譯器在基類中沒有匹配的虛函數時發出錯誤。
- 盡量避免在子類中隱藏基類中的非虛函數,除非這是有意為之。如果必須這樣做,請確保在文檔和注釋中清楚地說明這一點。
- 理解多態、重寫和隱藏的概念,并始終注意在涉及繼承時的函數簽名和訪問級別。*二、如果你遇到d2.doSomething(3.14)不能調用Base類的doSomething(double a)方法的情況,可能有幾個原因:①隱藏基類方法:如果在Derived2或它的直接基類Derived中有任何重載版本的doSomething方法,并且沒有使用using聲明來引入基類的方法,基類的方法可能會被隱藏。
這意味著,即使基類中存在doSomething(double a),如果派生類中定義了其他版本的doSomething(如doSomething(int)或doSomething(char)),
但沒有顯式地引用基類的方法,基類版本的方法在派生類對象上可能無法直接訪問。
②名稱隱藏:在C++中,如果派生類中定義了與基類相同名稱的成員函數,即使參數列表不同,基類中的同名函數也會被隱藏。
這是C++的名稱隱藏規則,不同于重載。
③訪問控制:如果Base類的doSomething方法是protected或private,則不能在Derived2類的對象上直接調用它(盡管這種情況不太可能,因為你提到了virtual,這通常意味著方法應該是public的)。另一方面,dp2->doSomething(3.14);可以工作,可能是因為dp2是一個指向Derived2的指針,但是被當做Base類的指針來使用。
當你通過基類指針調用一個virtual函數時,C++的多態性確保調用的是指針實際指向的對象的最具體的實現(即動態綁定)。
如果Derived2沒有重載doSomething(double a),則調用會回退到Base類的實現。如果d2.doSomething(3.14)不工作,而dp2->doSomething(3.14);工作,最可能的原因是名稱隱藏。
在派生類中定義新的doSomething函數時,如果沒有正確地使用using聲明來引入基類的函數,基類的同名函數會被隱藏
*/class Base {
public:virtual void doSomething(int a) {// 基類的doSomething(int)方法 std::cout << "Base::doSomething(int) called with " << a << std::endl;}virtual void doSomething(char a) {// 基類的doSomething(double)重載方法 std::cout << "Base::doSomething(char) called with " << a << std::endl;}virtual void doSomething(int a, int b, int c) {// 基類的doSomething(double)重載方法 std::cout << "Base::doSomething(int a,int b, int c) called with a:" << a << ", b:" << b << ", c:" << c << std::endl;}void doSomething(int a, int b, int c, int d) {// 基類的doSomething(double)重載方法 std::cout << "Base::doSomething(int a, int b, int c, int d) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;}void coutName() {// 基類的coutName重載方法 std::cout << "Base::coutName" << std::endl;}
};class Derived1 : public Base {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived1::doSomething(int) called with " << a << std::endl;}//using Base::doSomething;void doSomething(char c) {// 派生類重寫了基類的doSomething(char c)方法 std::cout << "Derived1::doSomething(char) called with " << c << std::endl;}
};class Derived2 : public Base {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived2::doSomething(int) called with " << a << std::endl;}virtual void doSomething(int a, int b, int c, int d) {// 派生類重寫了基類的doSomething(int a, int b, int c, int d)方法 std::cout << "Derived2::doSomething(double) called with a:" << a << ", b:" << b << ", c:" << c << ", d:" << d << std::endl;}
};class Derived3 : public Base {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived3::doSomething(int) called with " << a << std::endl;}//using Base::doSomething;void doSomething(int x, int y) {// 派生類重載了doSomething(int x, int y)方法std::cout << "Derived3::doSomething(int x, int y) called with x:" << x << ",y:" << y << std::endl;}
};class Derived11 : public Derived1 {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived11::doSomething(int) called with " << a << std::endl;}
};class Derived21 : public Derived2 {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived21::doSomething(int) called with " << a << std::endl;}
};class Derived31 : public Derived3 {
public://using Base::doSomething;void doSomething(int a) {// 派生類重寫了基類的doSomething(int)方法 std::cout << "Derived31::doSomething(int) called with " << a << std::endl;}
};int main() {cout << "************************************實例化對象開始表演******************************************************" << endl;Base b1;b1.doSomething(42);//b1.doSomething(3.14); // !!!編譯報錯提示"有多個重載函數 實例與參數列表相同,26和31行",因為3.14不知道轉換為int,還是charb1.doSomething('c');b1.doSomething(1, 2, 3);b1.doSomething(1, 2, 3, 4);b1.coutName();cout << endl << endl;Derived1 d1;d1.doSomething(42); // 調用Derived1的doSomething(int) //d1.doSomething(3.14); // !!!編譯報錯提示"有多個重載函數 實例與參數列表相同,56和62行",因為3.14不知道轉換為int,還是chard1.doSomething('c'); // !!!'c'轉化內int//d1.doSomething(1, 2, 3); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived1::doSomething, 參數類型為(int, int, int)"//d1.doSomething(1, 2, 3, 4); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived1::doSomething, 參數類型為(int, int, int, int)"d1.coutName(); // 調用基類的doSomething(double),因為派生類沒有重寫它 cout << endl << endl;Derived2 d2;d2.doSomething(42); // 調用Derived2的doSomething(int) d2.doSomething(3.14); // 3.14轉換為int,調用相同d2.doSomething('c'); // 'c'轉化內int//d2.doSomething(1, 2, 3);//!!!報錯提示"沒有與參數列表匹配的 重載函數Derived2::doSomething, 參數類型為(int, int, int)"d2.doSomething(1, 2, 3, 4); //!!!這是Derived2自身的函數,與Base沒有關系d2.coutName(); // 調用基類的doSomething(double),因為派生類沒有重寫它 // !!!子類Derived3::doSomething(int x, int y),是否對父類方法進行隱藏。也就是說d3是否擁有doSomething(double a)???cout << endl << endl;Derived3 d3;d3.doSomething(42); // 調用Derived2的doSomething(int) d3.doSomething(3.14); // 3.14轉換為int,調用相同d3.doSomething('c'); // 'c'轉化內int//d3.doSomething(1, 2, 3); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived3::doSomething, 參數類型為(int, int, int)"//d3.doSomething(1, 2, 3, 4); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived3::doSomething, 參數類型為(int, int, int, int)"d3.doSomething(1, 2); //!!!這是Derived3自身的函數,與Base沒有關系d3.coutName(); // 調用基類的doSomething(double),因為派生類沒有重寫它 cout << endl << endl;cout << "******************創建Derived1、Derived2、Derived3單獨的指針(感覺類實例化對象用法一致)*********************************************" << endl;Derived1* opd1 = new Derived1;opd1->doSomething(42); // 調用Derived1::doSomething(int) opd1->doSomething('c'); // 調用Derived1::doSomething(char)//opd1->doSomething(1, 2, 3); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived1::doSomething, 參數類型為(int, int, int)"//opd1->doSomething(1, 2, 3, 4);// !!!報錯提示"沒有與參數列表匹配的 重載函數Derived1::doSomething, 參數類型為(int, int, int, int)"opd1->coutName();cout << endl << endl;Derived2* opd2 = new Derived2;opd2->doSomething(42); // 調用Derived2的doSomething(int) opd2->doSomething('c'); // 調用Base::doSomething(char)//opd2->doSomething(1, 2, 3);//!!!報錯提示"沒有與參數列表匹配的 重載函數Derived2::doSomething, 參數類型為(int, int, int)"opd2->doSomething(1, 2, 3, 4); // !!!這是Derived2自身的函數,與Base沒有關系opd2->coutName();cout << endl << endl;Derived3* opd3 = new Derived3;opd3->doSomething(42); // 調用Derived2的doSomething(int) opd3->doSomething('c'); // 'c'轉化內int//opd3->doSomething(1, 2, 3);// !!!報錯提示"沒有與參數列表匹配的 重載函數Derived3::doSomething, 參數類型為(int, int, int)"//opd3->doSomething(1, 2, 3, 4); // !!!報錯提示"沒有與參數列表匹配的 重載函數Derived3::doSomething, 參數類型為(int, int, int, int)"opd3->doSomething(1, 2); // !!!報錯提示"沒有與參數列表匹配的 重載函數Base::doSomething, 參數類型為(int, int)"opd3->coutName();cout << endl << endl;cout << "******************創建基類指針分別指向Derived1、Derived2、Derived3(virtual在派生類中多態應用)**************************************" << endl;Base* pd1 = new Derived1;pd1->doSomething(42); // 調用Derived1::doSomething(int) pd1->doSomething('c'); // 調用Derived1::doSomething(char)pd1->doSomething(1, 2, 3);// 調用Base::doSomething(int, int, int)pd1->doSomething(1, 2, 3, 4);// 調用Base::doSomething(int, int, int, int)pd1->coutName();cout << endl << endl;Base* pd2 = new Derived2;pd2->doSomething(42); // 調用Derived2的doSomething(int) pd2->doSomething('c'); // 調用Base::doSomething(char)pd2->doSomething(1, 2, 3);// 調用Base::doSomething(int, int, int)pd2->doSomething(1, 2, 3, 4); // !!! 這里調用基類Base::doSomething(int, int, int, int),因為不是虛函數,子類Derived2沒有對其重寫pd2->coutName();cout << endl << endl;Base* pd3 = new Derived3;pd3->doSomething(42); // 調用Derived2的doSomething(int) pd3->doSomething('c'); // 'c'轉化內intpd3->doSomething(1, 2, 3);// 調用Base::doSomething(int, int, int)pd3->doSomething(1, 2, 3, 4);// 調用Base::doSomething(int, int, int, int)//pd3->doSomething(1, 2); // !!!報錯提示"沒有與參數列表匹配的 重載函數Base::doSomething, 參數類型為(int, int)"pd3->coutName();cout << endl << endl;cout << "******************創建Derived11、Derived21、Derived31單獨的指針(感覺類實例化對象用法一致)*********************************************" << endl;Derived11* opd11 = new Derived11;opd11->doSomething(42); // 調用Derived11::doSomething(int) opd11->doSomething('c'); // 'c'轉化內int//opd11->doSomething(1, 2, 3); // !!!報錯提示"參數太多",我感覺跟原來哪個意思一樣("沒有與參數列表匹配的 重載函數Derived11::doSomething, 參數類型為(int, int, int)")//opd11->doSomething(1, 2, 3, 4);// !!!報錯提示"參數太多"opd11->coutName();cout << endl << endl;Derived21* opd21 = new Derived21;opd21->doSomething(42); // 調用Derived21的doSomething(int) opd21->doSomething('c'); // 'c'轉化內int//opd21->doSomething(1, 2, 3);// !!!報錯提示"參數太多"//opd21->doSomething(1, 2, 3, 4); // !!!報錯提示"參數太多"opd21->coutName();cout << endl << endl;Derived31* opd31 = new Derived31;opd31->doSomething(42); // 調用Derived2的doSomething(int) opd31->doSomething('c'); // 'c'轉化內int//opd31->doSomething(1, 2, 3);;// !!!報錯提示"參數太多"//opd31->doSomething(1, 2, 3, 4);;// !!!報錯提示"參數太多"//opd31->doSomething(1, 2); ;// !!!報錯提示"參數太多"opd31->coutName();cout << endl << endl;cout << "******************創建基類指針分別指向Derived11、Derived21、Derived31(virtual在派生類中多態應用)**************************************" << endl;Base *pd11 = new Derived11;pd11->doSomething(42); // 調用Derived1::doSomething(int) pd11->doSomething('c'); // 調用Derived1::doSomething(char)pd11->doSomething(1, 2, 3);// 調用Base::doSomething(int, int, int)pd11->doSomething(1, 2, 3, 4);// 調用Base::doSomething(int, int, int, int)pd11->coutName();cout << endl << endl;Base *pd21 = new Derived21;pd21->doSomething(42); // 調用Derived2的doSomething(int) pd21->doSomething('c'); // 調用Base::doSomething(char)pd21->doSomething(1, 2, 3);// 調用Base::doSomething(int, int, int)pd21->doSomething(1, 2, 3, 4); // !!! 這里調用基類Base::doSomething(int, int, int, int),因為不是虛函數,子類Derived2沒有對其重寫pd21->coutName();cout << endl << endl;Base* pd31 = new Derived31;pd31->doSomething(42); // 調用Derived2的doSomething(int) pd31->doSomething('c'); // 'c'轉化內intpd31->doSomething(1, 2, 3);// !!!報錯提示"參數太多"pd31->doSomething(1, 2, 3, 4);// !!!報錯提示"參數太多"//pd31->doSomething(1, 2); // !!!報錯提示"參數太多"pd31->coutName();cout << endl << endl;return 0;
}