1. 程式人生 > >C++多型與繼承基本知識詳解

C++多型與繼承基本知識詳解

一、類繼承

C++是一種面向物件的語言,最重要的一個目的就是——提供可重用的程式碼,而類繼承就是C++提供來擴充套件和修改類的方法。類繼承就是從已有的類中派生出新的類,派生類繼承了基類的特性,同時可以新增自己的特性。實際上,類與類之間的關係分為三種:代理、組合和繼承。以下是三種關係的圖解:(為了更好的理解)


基類可以派生出派生類,基類也叫做“父類”,派生類也稱為“子類”。

那麼,派生類從基類中繼承了哪些東西呢?分為兩個方面:1. 變數——派生類繼承了基類中所有的成員變數,並從基類中繼承了基類作用域,即使子類中的變數和父類中的同名,有了作用域,兩者也不衝突。2.方法——派生類繼承了基類中除去建構函式、解構函式以外的所有方法。

二、繼承方式和訪問限定符

繼承方式有三種——public、protected和private,不同的繼承方式對繼承到派生類中的基類成員有什麼影響?見下圖:

總的來說,父類成員的訪問限定符通過繼承派生到子類中之後,訪問限定符的許可權小於、等於原許可權。其中,父類中的private成員只有父類本身及其友元可以訪問,通過其他方式都不能進行訪問,當然就包括繼承。protected多用於繼承當中,如果對父類成員的要求是——子類可訪問而外部不可訪問,則可以選擇protected繼承方式。

三、派生類物件的構造方式

前面也提到,派生類將基類中除去建構函式和解構函式的其他方法繼承了過來,那麼對於派生類物件中自己的成員變數和來自基類的成員變數,它們的構造方式是怎樣的呢?

答案是:1.先呼叫基類建構函式,構造基類部分成員變數,再呼叫派生類建構函式構造派生類部分的成員變數。2.基類部分成員的初始化方式在派生類建構函式的初始化列表中指定。3.若基類中還有成員物件,則先呼叫成員物件的建構函式,再呼叫基類建構函式,最後是派生類建構函式。析構順序和構造順序相反。見下:

#include <iostream>
using namespace std;
 
class Test
{
public:
	Test()
	{
		cout<<"Test::Test()"<<endl;
	}
private:
	int mc;
};
 
class Base
{
public:
	Base(int a)
	{
		ma = a;
		cout<<"Base::base()"<<endl;
	}
	~Base()
	{
		cout<<"Base::~base()"<<endl;
	}
private:
	int ma;
	Test t;
};
 
class Derive : public Base
{
public:
	Derive(int b):Base(b)
	{
		mb = b;
		cout<<"Derive::derive()"<<endl;
	}
	~Derive()
	{
		cout<<"Derive::~derive()"<<endl;
	}
private:
	int mb;
};
 
int main()
{
	Derive d(2);
	 
	return 0;
}

結果如下:

四、基類和派生類中同名成員的關係
派生類從基類中繼承過來的成員(函式、變數)可能和派生類部分成員(函式、變數)重名。1.前面提到,派生類從基類中繼承了基類作用域,所以同成員名變數可以靠作用域區分開(隱藏)。2.同名成員函式則有三種關係:過載、隱藏和覆蓋。

  1. 過載_overload
    函式過載有三個條件,一函式名相同,二形參型別、個數、順序不同,三相同作用域。根據第三個條件,可知函式過載只可能發生在一個類中,見下:

    class Base
    {
    public:
    Base(int a)
    {
    ma = a;
    }
    void show()
    {
    cout<<“base show 1”<<endl;
    }
    void show(int b)
    {
    cout<<“base show 2”<<endl;
    }
    private:
    int ma;
    };

其中,兩個show函式構成函式過載。
2. 函式隱藏_overhide

在派生類中將基類中的同名成員方法隱藏,要想在派生類物件中訪問基類同名成員得加上基類作用域。(注意,如果該同名方法在基類中實現了過載,在派生類物件中同樣需要指定作用域,而不能通過簡單的傳參,呼叫帶參過載方法)

#include <iostream>
using namespace std;
 
class Base
{
public:
	Base(int a)
	{
		ma = a;
	}
	void show()
	{
		cout<<"base show 1"<<endl;
	}
	void show(int b)
	{
                cout<<b<<endl;
		cout<<"base show 2"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b)
	{
		mb = b;
	}
	void show()
	{
		cout<<"derive show 1"<<endl;
	}
	
private:
	int mb;
};
int main()
{
	Derive d(1);
	d.show();
	d.Base::show();
	d.Base::show(2);
	 
	return 0;
}

結果如下:

  1. 函式覆蓋_override

    基類、派生類中的同名方法 函式頭相同(引數、返回值),且基類中該方法為虛擬函式,則派生類中的同名方法將基類中方法覆蓋。這裡涉及到了虛擬函式的問題,在後續進行講解。函式隱藏和函式覆蓋都是發生在基類和派生類之間的,可以這麼理解:基類和派生類中的同名函式,除去是覆蓋的情況,其他都是隱藏的情況。
    五、引用和指標

  2. 基類物件和派生類物件
    1.1 派生類物件可以賦值給基類物件
    1.2 基類物件不可以賦值給基類物件

對於基類物件和派生類物件,編譯器預設支援從下到上的轉換,上是基類,下是派生類。

  1. 基類指標(引用)和派生類指標(引用)
    2.1 基類指標(引用)可以指向派生類物件,但只能訪問派生類中基類部分的方法,不能訪問派生類部分方法
    2.2 派生類指標(引用)不可以指向基類物件,解引用可能出錯,因為派生類的一些方法可能基類沒有

編譯器只支援從上到下的轉換,即只能允許基類指標去指向派生類類物件。

以上對於方法的訪問都是基於指標的型別。我們可以看一下基類和派生類的大小,以及基類、派生類的指標(引用)的型別。

#include <iostream>
#include <typeinfo>
using namespace std;
 
class Base
{
public:
	Base(int a = 1)
	{
		ma = a;
	}
	void show()
	{
		cout<<"base show 1"<<endl;
	}
	void show(int b)
	{
		cout<<"base show 2"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
	}
	void show()
	{
		cout<<"derive show 1"<<endl;
	}
	
private:
	int mb;
};
int main()
{
	Base b;
	Derive d;
	Base* p = &d;
	cout<<"base size"<<sizeof(b)<<endl;
	cout<<"derive size"<<sizeof(d)<<endl;
	cout<<"p type"<<typeid(p).name()<<endl;
	cout<<"*p type"<<typeid(*p).name()<<endl;
	return 0;
}

結果如下:

分析:Base類和Derive類的大小就是他們各自包含的成員變數的總大小,Derive類繼承了Base類中的成員變數,所以要比Base類大4個位元組。在上面提到,這裡的方法呼叫都是依據指標的型別,所以我們可以看到 對基類指標p解引用得到的型別只和指標本身的型別相關。

其實,以上的方法指的是普通方法,對於特殊方法——虛擬函式的呼叫則完全不一樣!

六、虛擬函式

首先,我們看一下當Base*指向Derive物件時,而Base類中含有虛擬函式時,基類和派生類大小、基類和派生類指標(引用)的型別。

#include <iostream>
#include <typeinfo>
using namespace std;
 
class Base
{
public:
	Base(int a = 1)
	{
		ma = a;
	}
	virtual void show()
	{
		cout<<"base show 1"<<endl;
	}
	void show(int b)
	{
		cout<<"base show 2"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
	}
	void show()
	{
		cout<<"derive show 1"<<endl;
	}
	
private:
	int mb;
};
int main()
{
	Base b;
	Derive d;
	Base* p = &d;
	cout<<"base size:"<<" "<<sizeof(b)<<endl;
	cout<<"derive size:"<<" "<<sizeof(d)<<endl;
	cout<<"p type:"<<" "<<typeid(p).name()<<endl;
	cout<<"*p type:"<<" "<<typeid(*p).name()<<endl;
	return 0;
}

結果如下:

分析:當Base類中有虛擬函式時,不論是Base類還是Derive類,它們的大小都增加了4個位元組。並且當Base*指向Derive物件時,*Base的型別卻變為Derive,不再和指標本身的型別相關,這是怎麼回事呢?
  1. 虛擬函式指標

    實際上,Base和Derive類增加的4個位元組就是虛擬函式指標的大小,每一個類只要有虛擬函式(包括繼承而來的),它就有且只有一個虛擬函式指標,類的大小就是總的成員變數的大小加上一個虛擬函式指標的大小。虛擬函式指標指向的是一張虛表,裡面是這個類所有虛擬函式的地址,一個類對應一張虛擬函式表,而虛擬函式指標存在於每一個物件中,並且永遠佔據物件記憶體的前四個位元組。

    以含有虛擬函式的Base類為例,下面是它的記憶體佈局:

    class Base
    {
    public:
    Base(int a = 1)
    {
    ma = a;
    }
    virtual void show()
    {
    cout<<“base show 1”<<endl;
    }
    virtual int getnum()
    {
    return ma;
    }
    private:
    int ma;
    };

  2. 虛擬函式表

    虛擬函式表又稱為“虛表”,它在編譯期間就已經確定,在程式執行時就會被裝載到只讀資料段,在整個程式執行期間都會一直存在。一個類例項化的多個物件,它們 的虛擬函式指標指向的是同一張虛表。

    以上Base類中虛擬函式指標vfptr指向的虛擬函式表——vftable如下所示:

3.虛表的合併

Base類的派生類Derive類定義:

class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
	}
	void show()
	{
		cout<<"derive show 1"<<endl;
	}
	virtual int getval()
	{
		return mb;
	}
private:
	int mb;
};

上面已經給出了Base類的記憶體佈局,以下是仿照其畫出的Derive類的記憶體佈局:

實際上,Derive類的記憶體佈局並不是這樣,前面明確提到“只要有虛擬函式,就一定有且只有一個虛擬函式指標”,那麼派生類中只能有一個虛擬函式指標,又根據“虛擬函式指標永遠佔據物件的前四個位元組”原則,那麼正確的記憶體佈局見下:


相應地,派生類的虛擬函式表也有變化。如果派生類中實現了同名覆蓋函式,則派生類虛表中該同名覆蓋函式的地址會將基類該同名方法的地址覆蓋。派生類中如果沒有實現覆蓋,則裡面的同名函式地址還是基類方法的地址。Derive類中對show方法實現了同名方法覆蓋,則它的虛擬函式表為:

4.哪些方法不能實現為虛擬函式?

成員函式能實現為虛擬函式需要滿足兩個前提條件: 1.成員方法能取地址  2.成員方法依賴於物件。第一點毋庸置疑,虛擬函式表中需要儲存虛擬函式的地址。第二點,我們怎麼呼叫虛擬函式的?通過虛擬函式指標來找到虛表從而呼叫其中的方法,而虛擬函式指標又存在於物件中,所以這就意味著虛擬函式的呼叫需要依賴物件。

那麼,我們可以確定一些不能實現為虛擬函式的方法: 1.建構函式——建構函式就是用來建立物件的,如何將其實現為虛擬函式,使其依賴一個物件呼叫? 2.inline函式——行內函數直接在呼叫點展開,不能取地址 3.static方法——靜態方法是屬於整個類的,不依賴與單個物件。

5.靜態繫結和動態繫結

在編譯期就確定呼叫具體方法從而執行特定的函式程式碼稱為“靜態繫結”,在執行期才確定下呼叫哪個方法稱為“動態繫結”。實現“靜態繫結”的機制有:函式過載、模板。而實現“動態繫結”的機制是虛擬函式。

當方法沒有實現為virtual時,在編譯期就可以根據物件的型別、指標的型別來確定呼叫哪個方法,當實現為virtual時,就得在執行時通過物件中的虛擬函式指標,找到相應的虛擬函式表,得到方法地址,才能確定呼叫的是哪個方法,這時通過指標呼叫就和指標的型別無關。

Base類中實現了虛擬函式,Base*指向Derive物件時,*Base的型別變為Derive,這裡就涉及到虛表中的內容——RTTI。
5.1  虛表的結構  
上面關於虛表的結構是簡易版本,主要突出地址,以下是虛表的具體結構。
  
RTTI又稱為“執行時多型”,當基類中實現了虛擬函式時,基類指標指向派生類物件時,列印 *指標 型別得到的會是派生類型別。得到的 *指標 型別其實是 派生類的虛擬函式表中的RTTI內容。

5.2 動態繫結(執行時多型)
“執行時多型”和指標、引用呼叫方法相關,前提是該方法是虛擬函式。用基類指標指向基類物件、基類指標指向派生類物件和派生類指標指向派生類物件(引用同理),通過該指標或引用呼叫虛擬函式時,會發生多型——訪問指標指向物件的虛擬函式表,找到對應方法的地址,最後呼叫。用物件本身去呼叫方法(包括虛擬函式)時不會發生多型。

#include <iostream>
#include <typeinfo>
using namespace std;
 
class Base
{
public:
	Base(int a = 1)
	{
		ma = a;
	}
	virtual void show()
	{
		cout<<"base show 1"<<endl;
	}
	virtual int getnum()
	{
		return ma;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
	}
	void show()
	{
		cout<<"derive show 1"<<endl;
	}
	virtual int getval()
	{
		return mb;
	}
private:
	int mb;
};
int main()
{
	Base b;
	Derive d;
	Base* p = &d;
	cout<<"*p type: "<<typeid(*p).name()<<endl;
	p->show();
 
	return 0;
}

結果為:

6.抽象類和純虛擬函式
純虛擬函式沒有具體的實現,含有純虛擬函式的類稱為“抽象類”。抽象類不能例項化物件,只能作為基類,派生類可以繼承抽象類,對抽象類中的純虛擬函式實現 函式重寫覆蓋。
7.解構函式之虛擬函式

前面我們探討了那些不能實現虛擬函式的情況,解構函式是可以的。那麼什麼時候應該將解構函式實現為虛擬函式呢?答案是:當基類指標指向堆上開闢的派生類物件時。

class Base
{
public:
	Base(int a = 1)
	{
		ma = a;
		cout<<"Base"<<endl;		
	}
	~Base()
	{
		cout<<"~Base"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
		cout<<"Derive"<<endl;
	}
	~Derive()
	{
		cout<<"~Derive"<<endl;
	}
private:
	int mb;
};
int main()
{
	Base* p = new Derive(2);
	delete p;
	return 0;
}


我們可以看到,最後只調用了基類的解構函式,而派生類並沒有析構。因為這時呼叫方法就與指標的型別有關,指標型別是Base,則只調用Base的解構函式,只將Base部分的資料釋放。如果將解構函式實現為虛擬函式,則呼叫方法時就與指標型別無關,而是發生動多型——訪問到了派生類的虛擬函式表,呼叫派生類解構函式之後,再自動呼叫基類解構函式,整個析構過程才完整。

class Base
{
public:
	Base(int a = 1)
	{
		ma = a;
		cout<<"Base"<<endl;		
	}
	virtual ~Base()
	{
		cout<<"~Base"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b = 2):Base(b)
	{
		mb = b;
		cout<<"Derive"<<endl;
	}
	~Derive()
	{
		cout<<"~Derive"<<endl;
	}
private:
	int mb;
};
int main()
{
	Base* p = new Derive(2);
	delete p;
	return 0;
}
  1. 派生類中有虛擬函式指標,基類中沒有虛擬函式指標時,使基類指標指向派生類物件(new),delete基類指標時引發的一系列問題?

    class Base
    {
    public:
    Base(int a = 1)
    {
    ma = a;
    cout<<“Base”<<endl;
    }
    ~Base()
    {
    cout<<"~Base"<<endl;
    }
    private:
    int ma;
    };
    class Derive : public Base
    {
    public:
    Derive(int b = 2):Base(b)
    {
    mb = b;
    cout<<“Derive”<<endl;
    }
    virtual ~Derive()
    {
    cout<<"~Derive"<<endl;
    }
    private:
    int mb;
    };
    int main()
    {
    Base* p = new Derive(2);
    delete p;
    return 0;
    }

    new返回給基類指標的並不是派生類物件的起始地址,而是派生類物件中基類成員開始的地址,最後delete基類指標時的地址 != 派生類物件的起始地址(new是從哪裡開始分配記憶體的,就從哪裡開始回收delete),因而析構派生類部分時出錯。

    因此,應該避免這種情況,一定要將基類中的解構函式實現為虛擬函式,基類中也存在虛擬函式表之後,delete時就會發生解構函式的動多型,正確釋放空間。

  2. 虛擬函式表的寫入時間

    在編譯期間,虛擬函式表中已經將虛擬函式的地址寫好,並把虛擬函式表的地址寫到物件虛擬函式指標當中,我們來佐證以下這點:

    class Base
    {
    public:
    Base(int a):ma(a)
    {
    clear();
    }
    virtual ~Base(){}
    void clear(){memset(this,0,sizeof(this));}
    virtual void show()
    {
    cout<<“Base::”<<ma<<endl;
    }
    private:
    int ma;
    };
    class Derive : public Base
    {
    public:
    Derive(int b):Base(b),mb(b){}
    ~Derive(){}
    void show()
    {
    cout<<“Derive::”<<mb<<endl;
    }
    private:
    int mb;
    };
    int main()
    {
    Base
    p = new Base(10);
    p->show();
    delete p;

     return 0;
    

    }

執行結果如下:

執行p->show()時出錯了?因為發生執行時多型時,clear()將基類物件整個記憶體都置為0,此時基類的虛擬函式指標儲存的是0x00000000,虛表找不到,則show()函式的地址也找不到,調用出錯!

那麼,下面的程式碼可以執行成功嗎?

class Base
{
public:
	Base(int a):ma(a)
	{
		clear();
	}
	virtual ~Base(){}
	void clear(){memset(this,0,sizeof(*this));}
	virtual void show()
	{
		cout<<"Base::"<<ma<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b),mb(b){}
	~Derive(){}
	void show()
	{
		cout<<"Derive::"<<mb<<endl;
	}
private:	
	int mb;
};
int main()
{
	Base* p = new Derive(10);
	p->show();
	delete p;
 
	return 0;
}

不能?答案見下:

為什麼這下就執行成功了呢?不是已經把虛擬函式指標置零了嗎?

派生類物件的構造過程是怎麼的?先構造基類部分在構造派生類部分。在構造基類部分時,會將虛擬函式指標和虛表都初始化好,在這裡構造完之後就緊接著clear()置零了,但是後續還要構造派生類部分!這個過程會重寫虛擬函式指標,指標指向了Derive類對應的虛擬函式表,虛擬函式指標值由“0x00000000”改變成一個有效值。既然派生類物件中有一個有效的虛擬函式指標,那麼p->show()當然就能成功。

10.靜態繫結和動態繫結的時間

我們已經明確地知道:靜態繫結發生在編譯階段,動態繫結發生在執行階段。為了對此有更深刻的理解,這裡分以下幾點來延伸內容。

10.1 建構函式和解構函式中能不能實現動多型?

       在之前的討論中,我們確定了建構函式本身是不能寫成虛擬函式的,而解構函式必要時需要實現為虛擬函式。那麼在他們的函式體中能否實現多型,呼叫虛擬函式呢?

class Base
{
public:
	Base(int a):ma(a)
	{
		show();
	}
	virtual ~Base()
	{}
	virtual void show()
	{
		cout<<"Base::"<<ma<<endl;
	}
private:	
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b),mb(b)
	{
		show();
	}
	~Derive(){}
	void show()
	{
		cout<<"Derive::"<<mb<<endl;
	}
private:
	
	int mb;
};
int main()
{
	Base* p = new Derive(10);
	delete p;
	return 0;
}

結果執行成功,發生動多型了?讓我們來看看對應的彙編程式碼吧。

不論是基類部分的show()還是派生類的show(),彙編程式碼對應的都是“call 函式地址”,這是靜態繫結,這在編譯期間就已經確定了具體呼叫的函式。那麼,發生動多型的彙編程式碼又是什麼樣呢?


所以我們可以確定,靜態繫結對應的彙編程式碼總是“call 一個確切函式地址”,動態繫結對應的彙編程式碼“call eax”,eax暫存器中的值是不確定的,只有在執行時才會確定下來。解構函式中呼叫虛擬函式同樣也不會發生動多型,只是靜多型。

既然我們已經知道建構函式和解構函式中實現的是靜多型,那麼為什麼不能實現動多型呢?1. 虛擬函式實現的前提是——函式依賴於物件,建構函式就是來構造一個新物件的,如何實現動多型? 2. 解構函式的呼叫依賴於物件,所以解構函式可以實現為虛擬函式。可是在執行解構函式時,這個物件就在邏輯意義上消失了,生存週期結束,在這之中呼叫虛擬函式也是無法依賴物件呼叫的。

10.2 將子類中的虛方法放入private底下,通過基類指標呼叫該方法,編譯能成功嗎?

class Base
{
public:
	Base(int a):ma(a){}
	virtual ~Base(){}	
	virtual void show()
	{
		cout<<"Base::"<<ma<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b),mb(b){}
	~Derive(){}
private:
	void show()
	{
		cout<<"Derive::"<<mb<<endl;
	}
	int mb;
};
int main()
{
	Base* p = new Derive(10);
	p->show();
 
	delete p;
	return 0;
}

結果如下:

編譯、執行都通過了,為什麼呢?

編譯階段,訪問限定符public、protected和private發揮著作用,控制著外界對類成員的訪問。p是一個Base*型別,因此p只能看到Base類中的成員,即Base::show(),而它的訪問限定符為public——外界可訪問,因此編譯階段可以通過。到了執行階段後,因為基類中的show()是一個虛擬函式,所以發生動多型,最後執行的show()方法就是派生類的show方法。

注意:

1. 在派生類中,只要實現了和基類虛方法同函式頭的函式,不論它之前的限定符是何種,它和基類同名虛擬函式的關係都叫做“覆蓋”。

2. 執行期期間,限定符不發揮作用,因為找到虛擬函式表中對應的虛擬函式地址後,直接“call 函式地址”。

10.3 將基類虛擬函式放入protected或private中,利用基類指標呼叫該方法,編譯能通過嗎?

class Base
{
public:
	Base(int a):ma(a){}
	virtual ~Base(){}	
protected:
	virtual void show()
	{
		cout<<"Base::"<<ma<<endl;
	}
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b),mb(b){}
	~Derive(){}
	void show()
	{
		cout<<"Derive::"<<mb<<endl;
	}
private:
	int mb;
};
int main()
{
	Base* p = new Derive(10);
	p->show();
	delete p;
	return 0;
}

編譯不能通過。就如10.3中所說,編譯階段訪問限定符發揮著作用。Base類中的show()的限定符為protected,這意味著對外界不可見,只有本身和子類可以訪問,Base::show()對Base* p不可見,因此編譯錯誤。

10.4 當基類虛擬函式帶有預設值,派生類同名虛擬函式也帶有不同預設值時,通過基類指標呼叫派生類該方法時,預設值究竟是哪個?

class Base
{
public:
	Base(int a):ma(a){}
	virtual ~Base(){}	
	virtual void show(int a=30)
	{
		cout<<"Base::"<<a<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive(int b):Base(b),mb(b){}
	~Derive(){}
	void show(int b=80)
	{
		cout<<"Derive::"<<b<<endl;
	}
private:
	int mb;
};
int main()
{
	Base* p = new Derive(10);
	p->show();
 
	delete p;
	return 0;
}

結果如下:

最後得知,Derive::show()中的預設值變為了基類同名方法的預設值,為什麼呢?

因為,在編譯階段,呼叫函式之前需要壓引數,引數有預設值的話,壓入的就是確切的值。,main函式中 Base*-->Derive::show(),此時在產生的彙編程式碼中,壓入的引數就是Base::show()中的預設值30,在執行階段發生多型呼叫Derive中的方法,執行的還是這段彙編程式碼,即得到的預設值還是30。
總的來說,在編譯期間,函式預設值、是否可呼叫該函式、虛擬函式指標和虛表的內容都可以確定下來。

作者:帥氣的羊羊楊
來源:CSDN
原文:https://blog.csdn.net/qq_39755395/article/details/79751362
版權宣告:本文為博主原創文章,轉載請附上博文連結!