[C++]成員函數的重載(overload)、覆蓋(override)和隱藏(hide)

C++成員函數的重載、覆蓋和隱藏

ref: https://blog.csdn.net/wanghuiqi2008/article/details/28419645
ref: https://blog.csdn.net/zgbsoap/article/details/566120
ref: 《高質量程序設計指南 C++/C語言 第3版》

1. 重載與覆蓋

成員函數被重載的特徵是:

  • 具有相同的作用域(即同一個類定義中);
  • 函數名字相同;
  • 參數類型、順序或數目不同(包括);
  • virtual 關鍵字可有可無。

覆蓋是指派生類重新實現(或者改寫)了基類的成員函數,其特徵是:

  • 不同的作用域(分別位於派生類和基類中);
  • 函數名稱相同;
  • 參數列表完全相同;
  • 基類函數必須是虛函數。
#pragma once

#include <iostream>

using namespace std;

// 展示了 Overload 和 Override

class Base
{
public:
    void f(int x) { cout << "Base::f(int) " << x << endl; }
    // overload Base::f(int x)
    void f(float x) { cout << "Base::f(float) " << x << endl; }
    virtual void g(void) { cout << "Base::g(void)" << endl; }
};
class Derived : public Base
{
public:
    // override Base::g(void)
    virtual void g(void) { cout << "Derived::g(void)" << endl; }
};

void test()
{
    Derived d;
    Base *pb = &d;
    pb->f(42);      // Base::f(int) 42
    pb->f(3.14f);   // Base::f(float) 3.14
    pb->g();        // Derived::g(void)
}

注:

  1. virtual 關鍵字告訴編譯器,派生類中相同的成員函數應該放到 vtable 中,並替換基類相應成員函數的槽位;
  2. 虛函數的覆蓋有兩種方式:完全重寫和擴展。擴展是指派生類虛函數首先調用基類的虛函數,然後在增加新的功能。

2. 令人迷惑的隱藏規則

隱藏是指派生類的成員函數遮蔽了與其同名的基類成員函數,具體規則如下:

  • 派生類的函數與基類的函數同名,但是參數列表有所差異,此時,不論有無 virtual 關鍵字,基類的函數在派生類中將被隱藏(注意別於重載混淆);
  • 派生類的函數與基類的函數同名,參數列表也相同,但是基類函數沒有 virtual 關鍵字。此時,基類的函數在派生類中將被隱藏(注意別與覆蓋混淆)。
#pragma once

#include <iostream>

using namespace std;

// 展示了 Override 和 Hide

class Base
{
public:
    virtual void f(float x) { cout << "Base::f(float) " << x << endl; }
    void g(float x) { cout << "Base::g(float) " << x << endl; }
    void h(float x) { cout << "Base::h(float) " << x << endl; }
};
class Derived : public Base
{
public:
    // override Base::f()
    virtual void f(float x) { cout << "Derived::f(float) " << x << endl; }
    // hide Base::g()
    void g(int x) { cout << "Derived::g(int) " << x << endl; }
    // hide Base::h()
    void h(float x) { cout << "Derived::h(float) " << x << endl; }
};

void test()
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // Good : behavior depends solely on type of the object
    pb->f(3.14f); // Derived::f(float) 3.14
    pd->f(3.14f); // Derived::f(float) 3.14
    // Bad : behavior depends on type of the pointer
    pb->g(3.14f); // Base::g(float) 3.14
    pd->g(3.14f); // Derived::g(int) 3 (surprise!)
    // Bad : behavior depends on type of the pointer
    pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
    pd->h(3.14f); // Derived::h(float) 3.14
}

注:如果你確實想使用所謂的“跨越類邊界的重載”,可以在派生類定義中的任何地方顯示地使用 using 關鍵字,如下:

class Derived: public Base{
public:
    using Base::g;
    void g(int x) {...}
}

3. 擺脫隱藏

隱藏規則引起不少麻煩。下面代碼中,程序員可能期望語句 pd->f(10) 調用 Base::f(int),但是 Base::f(int) 不幸被Derived::f(char) 隱藏了。由於數字 10 不能被隱式地轉換爲字符串類型,所以在編譯時出錯。

#pragma once

#include <iostream>

using namespace std;

// 展示了 Hide 所帶來的麻煩

class Base
{
public:
    void f(int x) { cout << x << endl; }
};
class Derived : public Base
{
public:
    // hide Base::f(int)
    void f(char *str) { cout << str << endl; }

    // 擺脫隱藏法一
    //using Base::f;

    // 擺脫隱藏法二
    //void f(int x) { Base::f(x); }
};

void test(void)
{
    Derived *pd = new Derived;
    pd->f(10); // error 編譯時報錯,因爲Base中函數被隱藏
}

如果語句 pd->f(10) 確實想調用函數 Base::f(int),那麼有兩個辦法:其一就是使用 using 聲明;其二就是把類 Derived 修改爲如下的樣子。

class Derived : public Base
{
public:
    // hide Base::f(int)
    void f(char *str) { cout << str << endl; }
    // 調用傳遞
    void f(int x) { Base::f(x); }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章