1. 程式人生 > >C++虛擬函式簡析

C++虛擬函式簡析

  C++的虛擬函式是其實現多型的基礎,今天在這裡分享一下我對C++虛擬函式相關知識的系統總結,技術有限,如有不當,歡迎指正。
  在將內容前,將大致涉及到的內容圖解如下:
 這裡寫圖片描述
 
1. 有無虛擬函式在繼承中的區別

//-- Zuo add on 2018-04-07
class A
{
public:
    virtual void fun(){ std::cout << "A::fun"; }
    void fun1(){ std::cout << "A::fun1"; }
};

class B : public A
{
public:
    virtual
void fun(){ std::cout << "B::fun"; } void fun1(){ std::cout << "B::fun1"; } }; void main(char argc, char** argv) { A *a = new A; A *b = new B; a->fun(); b->fun(); a->fun1(); b->fun1(); }

輸出結果:

A::fun
B::fun
A::fun1
A::fun1

  可以看到,如果是虛擬函式,在繼承的時候,如果子類有重寫,就會被覆蓋(由子類的實現代替父類的實現),如果是普通成員函式,就不會被子類的實現覆蓋,這也就是多型的原型。
  例子很簡單,想必大家很容易看懂,但是這裡我提出兩個問題大家思想下,後面會有答案:
  1.1. 如果將上文的A *a = new B;

修改成 B *a = new B;結果會怎樣變化?
  1.2. 如果虛擬函式有預設值,預設值會有什麼特點?
2. 虛擬函式的本質
  虛擬函式的本質是因為C++給有虛擬函式的類增加了一張虛擬函式表,用來儲存所有虛擬函式的入口地址。在子類繼承父類的過程中,首先繼承的是這張虛擬函式表Virtual-Table(以下簡稱VT),如果子類有對父類虛擬函式的重寫,那麼就會在VT中覆蓋對應的函式地址。這樣就可以實現同樣的呼叫,在不同的子類裡,有不同的實現,這也就是多型
  注意一個問題,VT是跟著類走的,也就是說,如果是上文中1.1提到的,那麼VT的覆蓋就不會發生,因為A *a = new B;
等價於A *a = (A*)new B;正因為VT是跟著類走的,如果是B *a = new B;,那就無任何特別,普通的建立物件,如果是A *a = new B;,那在將B類轉換成A類的時候,也就是VT合併的時候。所以上文的1.1,輸出值就和class A毫無關係了。
  除了上面講到的,還有兩點特性:
   2.1. 只有虛擬函式的入口地址才會被儲存在VT中,如果是普通成員函式,當然不會儲存在裡面。
   2.2. 為了提高虛擬函式的呼叫效率,VT的地址被存放在類的最前面。
  我從網上找了一張圖比較明瞭:
  這裡寫圖片描述
  在繼承的過程在VT被子類重寫的虛擬函式地址覆蓋父類的虛擬函式地址,也就是同一個指標在不同的物件中可以指向不同的函式實現,這也就是虛擬函式的動態繫結實現多型的過程了。這個可以和普通函式的靜態繫結相對比,普通函式是在編譯期就靜態綁定了,而虛擬函式是在執行期通過VT儲存的函式地址實現動態繫結。
3. 純虛擬函式
  純虛擬函式是一種比虛擬函式更加極端的函式。它的形式如下:
  virtual void fun() = 0;
  它存在的目的是為了規範介面,使得子類必須要實現對應的介面,如果子類沒有實現介面,則會編譯報錯。
  使用純虛擬函式要注意一點,包含純虛擬函式的類被稱為抽象類,一般被設計為基類,且抽象類不能被例項化(因為有未實現的純虛擬函式)。
4. 安全性-訪問non-public虛擬函式
  VT的存在固然為實現C++的多型立下汗馬功勞,但是凡事都有雙面性,它的到來,也引入了C++的一些安全上的不足。上文我們說到了,為了提高多型呼叫的效能,C++將VT地址存放在類空間的段首位置,所以我們通過獲取類的地址可以找到VT的地址,也就是可以得到一個類所有虛擬函式的地址,那如果,這裡面的虛擬函式有是non-public的,那就破壞了C++的封裝屬性了。Show Code:

//-- Zuo add on 2018-04-07
class A
{
private:
    virtual void fun(){ qDebug() << "A fun"; }
};

void main(char argc, char** argv)
{
    A *a = new A;
    typedef void(*fun)(void);
    std::cout << "虛擬函式表地址 = " << (int*)(a) << std::endl;
    std::cout << "第一個虛擬函式地址 = " << (int*)(*(int*)a << std::endl;
    //-- 將虛擬函式地址轉換為void fun(void)函式指標
    fun f = (fun)*((int*)(*(int*)a));
    f();
}

可以看到,這裡可以無報錯的訪問到原本為private的虛擬函式。
5. 虛擬函式的預設值不能被覆蓋
  虛擬函式雖然可以被子類所覆蓋以形成多型,但是有一個細節還是要注意,虛擬函式的預設值是不能被覆蓋的,還是上面的程式碼:

//-- Zuo add on 2018-04-07
class A
{
public:
    virtual void fun(int a = 1){ std::cout << "A::fun && a = " << a; }
};

class B : public A
{
public:
    virtual void fun(int a = 2){ std::cout << "B::fun && a = " << a; }
};

void main(char argc, char** argv)
{
  A *a = new A;
  A *b = new B;
  a->fun();
  b->fun();
}

輸出結果:

A::fun && a = 1
B::fun && a = 1

可以看到這裡的a沒有被改變。

相關推薦

C++虛擬函式

  C++的虛擬函式是其實現多型的基礎,今天在這裡分享一下我對C++虛擬函式相關知識的系統總結,技術有限,如有不當,歡迎指正。   在將內容前,將大致涉及到的內容圖解如下:     1. 有無虛擬函式在繼承中的區別 //-- Zuo add on

c++友元函式

在實現類之間資料共享時,減少系統開銷,提高效率。如果類A中的函式要訪問類B中的成員(例如:智慧指標類的實現),那麼類A中該函式要是類B的友元函式。具體來說:為了 使其他類的成員函式直接訪問該類的私有變數。即:允許外面的類或函式去訪問類的私有變數和保護變數,從而使兩個類共享同一函式。 3.友元函式和普通

C++虛擬函式表以及記憶體對齊文章

C++虛擬函式表以及記憶體對齊文章 C++ 物件的記憶體佈局(上) https://blog.csdn.net/haoel/article/details/3081328 C++ 物件的記憶體佈局(下) https://blog.csdn.net/haoel/article/deta

c# 虛擬函式Virtual與重寫override

C#程式碼   using System; namespace Smz.Test { class A { public virtua

【轉】C++虛擬函式

引言 C++中的虛擬函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父類型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現可變的演算法。比如:模板技術,RTTI技術,虛擬函

C++虛擬函式表在虛繼承和繼承中的差別

下面的程式碼在gcc和VC中的結果 #include <cstdio> class A { public: virtual void funcaa() { printf("class A %s\n",__func__); } }; class AA:virtual pu

C++虛擬函式本質論

C++虛擬函式本質論 13.2.1 多型性原理 1.多型性概念 對多型性最簡單的理解就是一種事物有多種形態。在面向物件設計中,多型性指的是向不同的物件傳送同一訊息時,會產生不同的動作(或行為、功能)。所謂的“向不同的物件傳送同一訊息”,其實就是指呼叫不同物件的某個函式;而“產生不同的動作

回撥函式

//類Aclass A {   public:    A();   void func();   private:   B m_b; } //類B typedef void (MyFunc)(); class B { public: B(MyFunc f);private:

C++ 虛擬函式的內部實現

單繼承的情況下 若類有虛擬函式,則在建構函式的時候編譯器會自動為類的例項(物件)在其記憶體的首部(0地址偏移處)增添一個虛擬函式表指標vfptr,指向該類的虛擬函式表。虛擬函式表中會存放該類所有的虛擬函式地址,普通函式則不會被放入其中。如果是子類重寫了父類的虛擬函式,那麼在建立虛擬函

c++虛擬函式(override)和過載函式(overload)的比較

1. 過載函式要求函式有相同的函式名稱,並有不同的引數序列;而虛擬函式則要求完全相同; 2. 過載函式可以是成員函式或友元函式,而虛擬函式只能是成員函式; 3. 過載函式的呼叫是以所傳遞引數的差別作為呼叫不同函式的依據,虛擬函式是根據物件動態型別的不同去呼叫不同

C++ 虛擬函式的兩個例子

1. 第一個例子是朋友告訴我Qt中的某個實現 1 #include <iostream> 2 3 // Qt中的某個實現 4 class A{ 5 public: 6 A() = default; 7 virtual ~A() = default; 8 9 virtua

深入淺出理解c++虛擬函式

    深入淺出理解c++虛擬函式   記得幾個月前看過C++虛擬函式的問題,當時其實就看懂了,最近筆試中遇到了虛擬函式竟然不太確定,所以還是理解的不深刻,所以想通過這篇文章來鞏固下。   裝逼一刻: 最近,本人思想發生了巨

理解C++虛擬函式

1、簡單介紹   C++虛擬函式是定義在基類中的函式,子類可以選擇重寫。在類中宣告(沒有寫函式體的為宣告)虛擬函式的格式如下: virtual void display(); 2、虛擬函式的作用   在使用指向子類物件的基類指標,並呼叫子

C++ 虛擬函式的預設引數問題

前些日子,有個同學問我一個關於虛擬函式的預設引數問題。他是從某個論壇上看到的,但是自己沒想通,便來找我。現在分享一下這個問題。先看一小段程式碼: #include <iostream> using namespace std; class A

C++虛擬函式表(含測試程式碼)

自己搞不懂C++虛擬函式之間的呼叫關係,特地花費一個下午加一個晚上查資料學習,現在把學到的發上來,供大家學習批評; 在此之前感謝這些大佬的部落格等,為我解惑甚多: 1、虛表與虛表指標 C++中的虛擬函式的實現一般是通過虛擬函式表(V-Table)來實

C++ 虛擬函式表解析

1 前言 C++中的虛擬函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現

C++虛擬函式

2018年11月12日 15:56:57 ChessC 閱讀數:7 個人分類: 一些總結

C++虛擬函式(2)

class x { char *p; public: x(int sz) {p=new char[sz];} virtual ~x(){cout << "~x()\n"; delete []p;} }; class y : public x {

C++ 虛擬函式表指標以及虛擬函式指標的確定

【摘要】 很多教材上都有介紹到虛指標、虛擬函式與虛擬函式表,有的說類物件共享一個虛擬函式表,有的說,一個類物件擁有一個虛擬函式表;還有的說,無論使用者聲明瞭多少個類物件,但是,這個VTABLE虛擬函式表只有一個;也有的在說,每個具有虛擬函式的類的物件裡面都有一

C++ 虛擬函式 筆試題目--綠盟科技

求下面程式的輸出值: class A{ public:  A() {func(0);};  virtual void func(int data) {printf("A1 :%d\n",data);}  virtual void func(int data) const {