日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++虛函數注意事項_C 語言

作者:?梁唐 ? 更新時間: 2022-03-23 編程語言

文章轉自公眾號:Coder梁(ID:Coder_LT)

一、虛函數注意事項

在之前的文章當中,我們已經討論了虛函數的使用方法,也對它的原理進行了簡單的介紹。

這里簡單做一個總結:

  • 在基類的方法聲明中使用關鍵字virtual可以聲明虛函數
  • 加上了virtual關鍵字的函數在基類以及派生類和派生類再派生出來的類中都是虛的
  • 在調用虛函數時,程序將會根據對象的類型執行對應的方法而非引用或指針的類型
  • 在定義基類時,需要將要在派生類中重新定義的類方法聲明為虛,如析構函數

除了這些之外,我們還有一些其他需要注意的事項。

1.構造函數

構造函數不能是虛函數,創建派生類對象時將調用派生類的構造函數,而非基類的構造函數,畢竟構造函數是根據類名調用的。

一般我們會在派生類中調用基類的構造函數,這其實不是繼承機制,所以將類構造函數聲明為虛沒有意義。

2.析構函數

前文說過析構函數應該是虛函數,除非類不被繼承。

因為派生類當中往往含有獨有的成員變量,如果析構函數非虛,那么會導致在對象析構時僅調用基類的析構函數,從而導致獨有的成員變量內存不被釋放,引起內存泄漏。

所以通常我們會將析構函數設置成virtual,即使不用做基類也不會引起錯誤,至多只會影響一點效率。但在大型合作開發的項目當中,許多組件和類都是共享的,我們往往無法保證我們開發的類是否會被其他開發者繼承,因此設置虛析構函數也是一種常規做法。

3.友元

友元函數不能是虛函數,因為友元不是類成員,只有成員函數才能是虛函數。

如果我們希望友元函數也能實現類似虛函數的功能, 我們可以在友元函數當中使用虛函數來解決。

4.沒有重新定義

如果派生類當中沒有重新定義虛函數,那么將使用該函數的基類版本。如果派生類位于派生鏈中,如B繼承了A,C繼承了B這種情況,那么派生類將會使用最新的虛函數版本。

5.重新定義將隱藏方法

我們來看一個例子:

class Mammal {
?private:
? string name;
?public:
? Mammal(string n): name(n) {}
? virtual void speak() const {
? ?cout << "can't say anything" << endl;
? }
};

class Human : public Mammal{
?private:
? string job;
?public:
? Human(string n, string j): Mammal(n), job(j) {}
? virtual void speak(const string st) const {
? ?cout << "i'm human" << endl;
? }
};

我們在父類當中定義了一個無參虛函數speak,而在子類Human當中也定義了一個需要傳入一個string類型的虛函數speak

我試了一下,在我的g++編譯器當中,會報錯:

但根據C++ Primer中的說法,在一些古老的編譯器當中,可能不會報錯,甚至可能連警告都沒有。

在這類編譯器當中,我們重新定義父類中的虛函數,這樣的重新定義不會生成兩個重載版本,而是隱藏了父類無參的版本,只保留了接受string類型的版本,這種情況有別于函數重載。

在派生類當中重新定義函數,不是使用相同的函數特征標覆蓋基類聲明,而是隱藏同名的基類方法,不管函數特征標如何。

C++ Primer當中給出了兩條經驗規則:

如果重新定義繼承的方法,應該保證與原來的原型完全相同,唯一的例外是返回的類型,如果基類返回的是基類的引用或指針,派生類可以改成派生類的引用或指針:

class Mammal {
?private:
? string name;
?public:
? Mammal(string n): name(n) {}
? virtual Mammal* build();
};

class Human : public Mammal{
?private:
? string job;
?public:
? Human(string n, string j): Mammal(n), job(j) {}
? virtual Human* build();
};

如果基類聲明被重載了,那么應該在派生類中聲明所有的基類版本:
?

class Mammal {
?private:
? string name;
?public:
? Mammal(string n): name(n) {}
? virtual void speak() const ;
? ? ?virtual void speak(int n) const;
? ? ?virtual void speak(const string st) const;
};

class Human : public Mammal{
?private:
? string job;
?public:
? Human(string n, string j): Mammal(n), job(j) {}
? virtual void speak() const ;
? ? ?virtual void speak(int n) const;
? ? ?virtual void speak(const string st) const;
};

如果我們只重新定義了一個版本,那么另外兩個版本將隱藏。

但這可能和編譯器版本有關,在新版的編譯器當中似乎取消了這一設定。

我嘗試了一下,發現并不會隱藏,一樣可以順利調用父類方法。

class Mammal {
?private:
? string name;
?public:
? Mammal(string n): name(n) {}
? virtual void speak() const {
? ?cout << "can't say anything from empty" << endl;
? }
? virtual void speak(const string st) const {
? ?cout << "can't say anything from string input" << endl;
? }
};

class Human : public Mammal{
?private:
? string job;
?public:
? Human(string n, string j): Mammal(n), job(j) {}
? virtual void speak(const string st) const {
? ?cout << "i'm human" << endl;
? }
};

int main() {
?Mammal *m = new Human("man", "spiderman");
?m->speak();
?return 0;
}
欄目分類
最近更新