高質量C/C++程式設計指南總結(八)—— C++高階特性

墨小语發表於2024-07-18

1. 成員函式過載特徵

  • 相同的範圍(在同一個類中)
  • 函式名稱相同
  • 引數不同
  • virtual 關鍵字可有可無

2. 覆蓋的特徵

  • 覆蓋是指派生類函式覆蓋基類函式,所以範圍不同(分別位於派生類和基類)
  • 函式名稱相同
  • 引數相同
  • 基類函式必須有 virtual 關鍵字

如下示例中,函式 Base::f(int)與 Base::f(float)相互過載,而 Base::g(void)被 Derived::g(void)覆蓋。

#include <iostream.h>
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:
    virtual void g(void){ cout << "Derived::g(void)" << endl;}
};

void main(void)
{
    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)
}

3. 隱藏規則

  • 如果派生類的函式與基類函式同名,但是引數不同。此時,無論有無 virtual 關鍵字,基類的函式都被隱藏。(主要與過載的區別,過載要在同一個類中)
  • 如果派生類的函式與基類函式同名,並且引數也相同,但是基類函式沒有 virtual 關鍵字。此時,基類的函式被隱藏。(注意與覆蓋的區別,覆蓋有 virtual 關鍵字

如下示例中:

    • 函式 Derived::f(float)覆蓋了 Base::f(float)
    • 函式 Derived::g(int)隱藏了 Base::g(float),而不是過載。
    • 函式 Derived::h(float)隱藏了 Base::h(float),而不是覆蓋。
#include <iostream.h>
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:
    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; }
};

4. 引數的預設值

  • 引數預設值只能出現在函式的宣告中,而不能出現在定義體中。
  • 如果函式有多個引數,引數只能從後向前挨個兒預設。
  • 不合理地使用引數的預設值將導致過載函式產生二義性。

5. 不能被過載的運算子

  • 不能改變 C++內部資料型別(如 int,float 等)的運算子。
  • 不能過載‘ .’,因為‘ .’在類中對任何成員都有意義,已經成為標準用法。
  • 不能過載目前 C++運算子集合中沒有的符號,如#,@,$等。原因有兩點,一是難以
    理解,二是難以確定優先順序。
  • 對已經存在的運算子進行過載時,不能改變優先順序規則,否則將引起混亂。

6. 函式內聯

1)內聯的工作過程:

對於任何行內函數,編譯器在符號表裡放入函式的宣告(包括名字、引數型別、返回值型別)。如果編譯器沒有發現行內函數存在錯誤,那麼該函式的程式碼也被放入符號表裡。在呼叫一個行內函數時,編譯器首先檢查呼叫是否正確(進行型別安全檢查,或者進行自動型別轉換,當然對所有的函式都一樣)。如果正確,行內函數的程式碼就會直接替換函式呼叫,於是省去了函式呼叫的開銷。這個過程與預處理有顯著的不同,因為前處理器不能進行型別安全檢查,或者進行自動型別轉換。假如行內函數是成員函式,物件的地址( this)會被放在合適的地方,這也是前處理器辦不到的。

2)行內函數的程式設計風格

  • 關鍵字 inline 必須與函式定義體放在一起才能使函式成為內聯,僅將 inline 放在函式宣告前面不起任何作用。
  • 定義在類宣告之中的成員函式將自動地成為行內函數。

3)內聯的注意事項

    • 內聯是以程式碼膨脹(複製)為代價,僅僅省去了函式呼叫的開銷,從而提高函式的執行效率。如果執行函式體內程式碼的時間,相比於函式呼叫的開銷較大,那麼效率的收穫會很少。另一方面,每一處行內函數的呼叫都要複製程式碼,將使程式的總程式碼量增大,消耗更多的記憶體空間。
    • 以下情況不宜使用內聯:
      1)如果函式體內的程式碼比較長,使用內聯將導致記憶體消耗代價較高。
      2)如果函式體內出現迴圈,那麼執行函式體內程式碼的時間要比函式呼叫的開銷大。
    • 類的建構函式和解構函式容易讓人誤解成使用內聯更有效。要當心建構函式和解構函式可能會隱藏一些行為,如“偷偷地”執行了基類或成員物件的建構函式和解構函式。所以不要隨便地將建構函式和解構函式的定義體放在類宣告中。

相關文章