C++中重載、覆蓋以及隱藏的區別

C++中重載、覆蓋以及隱藏的區別

C++中重載、覆蓋以及隱藏是經常讓人混淆的三個概念。
1、重載
有兩個或多個函數名相同的函數,但是函數的形參列表不同。在調用相同函數名的函數時,根據形參列表確定到底該調用哪一個函數。

重載是C++提供的一種靈活運用的操作,它不止可以用到函數,還可以運用到運算符中進行重載。

重載的函數必須位於同一個命名空間中,在類的層次上來看,每個類就相當於是一個命名空間,所以重載只能發生在同一個類中

成員函數被重載的特徵:

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無。

舉例:

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

int main()
{
    Derived d;

    d.f(42);
    d.f(3.14f);
 
    return 0;
}

運行結果:


2、覆蓋
在基類中定義了一個虛擬函數,然後在派生類中又定義了一個同名同參數同返回類型的函數,這就是覆蓋了。

在派生類對象上直接調用這個函數名,只會調用派生類中的那個。

覆蓋是發生在不同類之間(基類和派生類)。
覆蓋是指派生類函數覆蓋基類函數,特徵是:
(1)不同的範圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。

舉例:
class Base
{
    public:
    void f(int x){ cout << "Base::f(int) " << x << endl; }
    void f(float x){ cout << "Base::f(float) " << x << endl; }
    virtual void g(void){ cout << "Base::g(void)" << endl;}
};

class Derived : public Base
{
    public:
    void g(void){ cout << "Derived::g(void)" << endl;}
};
int main()
{
    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)

    return 0;
}

運行結果:


注意:此時如果將基類中函數g()前的virtual去掉,會是什麼結果?答案是,這種情況不會構成覆蓋。


2、覆蓋

既然是和虛擬函數掛鉤,說明了這個是一個多態支持的特性,所謂的覆蓋指的是用基類對象的指針 或者引用時訪問虛擬函數的時候會根據實際

的類型決定所調用的函數,因此此時派生類的成員函數可以"覆蓋"掉基類的成員函數注意唯有同名且參數相同還有帶有virtual關鍵字並且分別

在派生類和基類的函數才能構成虛擬函數,這個也是派生類的重要特徵而且,由於是和多態掛鉤的,所以只有在使用類對象指針或者引用的時候

才能使用上總之一句話:覆蓋函數都是虛函數,反之不然~~ (如果基類和繼承類的函數名稱,產生返回值都是一樣的[如果返回值不同應該無法

編譯],如果基類用到了virtual,那麼無論繼承類的實現中是否加入virtual 這個keyword ,還是會構成覆蓋的關係)。

多態:
在基類中定義了一個虛擬函數,然後在派生類中又定義一個同名,同參數表的函數,這就是多態。多態是這3種

情況中唯一採用動態綁定技術的一種情況。也就是說,通過一個基類指針來操作對象,如果對象是基類對象,

就會調用基類中的那個函數,如果對象實際是派生類對象,就會調用派聲類中的那個函數,調用哪個函數並不

由函數的參數表決定,而是由函數的實際類型決定。
舉例:

class A
{
    public:
    virtual void ShowMessage(){cout<<"Hello,This is A.\n";} // 分析加不加virtual的區別
};

class B:public A
{
    public:
    void ShowMessage(){cout<<"Hello,This is B.\n";}
};

int main()
{
    A* p;

    p=new A();
    p->ShowMessage();

    p=new B();
    p->ShowMessage();

    return 0;
}

運行結果:


3、隱藏
的是派生類的成員函數隱藏了基類函數的成員函數.隱藏一詞可以這麼理解:在調用一個類的成員函數的時候,編譯器會沿着類的繼承鏈逐級

的向上查找函數的定 ,如果找到了那麼就停止查找了,所以如果一個派生類和一個基類都有同一個同名(暫且不論參數是否相同)的函數,而編

譯器最終選擇了在派生類中的函數,麼我們就說這個派生類的成員函數"隱藏"了基類的成員函數,也就是說它阻止了編譯器繼續向上查找函數

的定義回到隱藏的定義中,前面已經說了有virtual關鍵字並且分別位於派生類和基類的同名,同參數函數構成覆蓋的關係,因此隱藏的關係只有

如下的可能:
1)必須分別位於派生類和基類中 ;
2)必須同名;
3)參數不同的時候本身已經不構成覆蓋關係了,所以此時是否是virtual函數已經不重要了。
當參數相同的時候就要看時候有virtual關鍵字了,有的話就是覆蓋關係,沒有的時候就是隱藏關係了很多人分辨不清隱藏和覆蓋的區別,因爲他們

都是發生在基類和派生類之中的.但是它們之間最爲重要的區別就是覆蓋的函數是多態的,是存在於vtbl之中的函數才能構成"覆蓋"的關係,

隱藏的函數都是一般的函數,不支持多態,在編譯階段就已經確定下來了。
直接使用類對象纔會出現隱藏。
舉例:

struct foo
{
    void func(int x)
    {
        cout <<"foo::func(int)"<<endl;
    }
    void func(float x)
    {
        cout <<"foo::func(float)"<<endl;
    }
    void func(double x)
    {
        cout <<"foo::func(double)"<<endl;
    }
};

struct a
{
    void func(int x)
    {
        cout <<"struct a :: fun(int x)"<<endl;
    }
};

struct b : public a
{
    void func(float x)
    {
        cout <<"struct b::fun(float x)"<<endl;
    }
};

struct c : public b
{
    //using a::func;
    //using b::func;
    void func(double x)
    {
        cout <<"struct c::func(double x)"<<endl;
    }
};

int main()
{
    foo  fo;
    fo.func(1);
    fo.func((float)1);

    c  oc; // 直接根據類對象來調用
    oc.func(1);
    oc.func((float)1);
}

注意:怎麼樣取消隱藏?可以申明出基類命名空間(每個類都相當於是一個獨立的命名空間),這樣隱藏就變成了重載,因爲不同的函數成

爲了一個命名空間(注意,此時基類和派生類中的同名函數的參數一定要不同)。

struct foo
{
    void func(int x)
    {
        cout <<"foo::func(int)"<<endl;
    }
    void func(float x)
    {
        cout <<"foo::func(float)"<<endl;
    }
    void func(double x)
    {
        cout <<"foo::func(double)"<<endl;
    }
};

struct a
{
    void func(int x)
    {
        cout <<"struct a::fun(int x)"<<endl;
    }
};

struct b : public a
{
    void func(float x)
    {
        cout <<"struct b::fun(float x)"<<endl;
    }
};

struct c : public b
{
    using a::func;
    using b::func;
    void func(double x)
    {
        cout <<"struct c::func(double x)"<<endl;
    }
};

int main()
{
    foo  fo;
    fo.func(1);
    fo.func((float)1);

    c  oc;
    oc.func(1);
    oc.func((float)1);
}

運行結果:


總結:在基類和派生類中,如果直接使用類對象進行操作,容易引起隱藏。當使用指針調用類的操作時,如果該成員函數已用virtual進行

了聲明,那麼會發生覆蓋,這時候具體的操作應該依據指針所指的對象類型;反之,當沒有virtual時,不會發生覆蓋,這時候具體的操作

要依據指針的類型來進行判斷。
舉例:

class Base
{
    public:
    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
    void g(float x){ cout << "Base::g(float) " << x << endl; } // 加不加virtual沒有關係,派生類與基類該函數的參數不同
    void h(float x){ cout << "Base::h(float) " << x << endl; }
};

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

int main()
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // Good : behavior depends solely on type of the object(定義爲虛函數virtual)
    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(未定義爲虛函數virtual)
    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!)(未定義爲虛函數virtual)
    pd->h(3.14f); // Derived::h(float) 3.14

    // 隱藏基類的操作
    d.f(3.14f);
    d.g(3.14f);
    d.h(3.14f);

    return 0;
}

運行結果:


最後,再提醒一個值得注意的地方,經常遇到的虛析構函數,實質上就是覆蓋操作。虛析構函數(基類的析構函數前+virtual)的使用主要

是爲了防止內存的泄露,詳情見我上篇博文《深度解析構造函數和析構函數》(http://blog.csdn.net/shenbo2030/article/details/44

588203)。
舉例:

class ClxBase
{
public:
    ClxBase() {};
    virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;} // 加不加virtual的區別

    virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; } // 加不加virtual的區別
};

class ClxDerived : public ClxBase
{
public:
    ClxDerived() {};
    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }

    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};

int main()
{
    //ClxBase Test;
    ClxBase *pTest = new ClxDerived;
    pTest->DoSomething();
    delete pTest;

    return 0;
}

運行結果:


如果基類中析構函數前不加virtual,則運行結果如下(派生類沒有進行釋放),此時會造成內存泄露,產生無法預測的錯誤。





發佈了25 篇原創文章 · 獲贊 18 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章