讀者注意:閱讀這篇文章時,對繼承中的對象模型要有一定了解;因爲本篇文章有的例子沒有給出類的成員變量,如果不瞭解繼承的對象模型,琢磨起來可能會很慢,覺得自己不確定的話單擊下面的“繼承”。
多態
多態按字面的意思就是“多種狀態”。在面嚮對象語言中,接口的多種不同的實現方式即爲多態。直白點理解就是不同的對象收到相同的消息時,產生不同的動作。(隨風倒)
多態可以分爲靜態多態和動態多態。
靜態多態:是在編譯器期間完成的,也叫早期綁定;
動態多態:在程序執行期間(非編譯期)判斷所引用對象的實際類型,根據其實際類型調用相應的方法,也叫動態綁定。
動態多態的構成條件:要有虛函數,並且一定要重寫,一個在基類,一個在派生類。(虛函數的函數原型、返回值、函數名、參數列表都必須一樣),必須通過基類類型的引用或者指針調用虛函數。
舉個簡單的例子:
class A
{
public:
A(int x)//構造函數
{
a = x;
}
virtual void print()//虛函數
{
cout << "A::" << a << endl;
}
private:
int a;
};
class B :public A
{
public:
B(int x, int y)
:A(x)
{
b = y;
}
virtual void print()//重寫
{
cout << "B::" << b << endl;
}
private:
int b;
};
void test()
{
A a(10);
B b(15,20);
A *c = &a;
c->print();
c = &b;
c->print();
}
有一個例外:協變;返回值類型不同(基類裏面返回基類的引用或指針,派生類裏面返回派生類的引用或指針)
重載&同名隱藏&重寫三者的區別
重載:重載只能是類的成員函數,必須在同一個作用域內(也就是同一個類裏),成員函數的名字必須相同,參數列表(個數,順序,類型)可以不同。
同名隱藏:只要成員(可以使成員變量也可以是成員函數)的名字一樣(一個在基類,一個在派生類),和參數列表、返回值沒有關係,並且要用派生類的對象來調用同名成員。
重寫:有虛函數,並且一個在基類,一個在派生類,函數的原型相同(協變除外)
哪些成員函數可以作爲虛函數,可不可採用?哪些不可以作爲虛函數?
1.構造函數(拷貝構造)
不可以,因爲有了構造函數才能構造出對象,對象中纔能有虛表;而虛函數先調用虛表,在找到虛表中的構造函數;兩者前後矛盾。
2.內聯函數
不可以,原因其實很簡單,那內聯函數就是爲了在代碼中直接展開,減少函數調用花費的代價,虛函數是爲了在繼承後對象能夠準確的執行自己的動作,這是不可能統一的。(再說了,inline函數在編譯時被展開,虛函數在運行時才能動態的邦定函數)
3.靜態成員函數
不可以,因爲靜態成員函數是全局的,對於所有的類來說,是一份公共的代碼,它不歸某個具體對象所有,所以他也沒有要動態邦定的必要性。
4.友元函數
不可以,因爲C++不支持友元函數的繼承,對於沒有繼承特性的函數沒有虛函數的說法。
5.賦值運算符重載
可以,但是不採用,雖然可以將operator=定義爲虛函數,但最好不要這麼做,使用時容易混淆。
6.析構函數
可以,而且最好將基類的析構函數聲明爲虛函數。因爲析構函數比較特殊,因爲派生類的析構函數跟基類的析構函數名稱不一樣,但是構成覆蓋,這裏編譯器做了特殊處理,減少了空間開銷。
繼承
在上一篇博客中我們提到了繼承的對象模型,這一次我們依然談到繼承的對象模型,只不過這次在類種新增了虛函數。說到虛函數,我們先剖析一下,舉個簡單的例子:
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
typedef void(*PVTF)();
void PrintVpt()
{
A a;
PVTF* pVtf = (PVTF*)(*(int*)&a);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
PrintVpt();
}
也就是說在一個類裏定義了虛函數,那麼創建實例化對象之後,編譯器會創建一張虛表,對象的前四個字節存放虛表的地址,虛表裏存放各個虛函數的地址。
單繼承
第一種情況:派生類中沒有重寫虛函數,也沒有自己的虛函數,則直接繼承基類的虛函數,派生類會重新生成一張虛表,基類裏的虛表不會繼承下來。(派生類中的虛表地址和基類的虛表地址不一樣,虛表裏地址也不一樣,但是虛函數是一樣的)
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
class B :public A
{
};
typedef void(*PVTF)();//函數指針
void PrintVpt()//打印虛表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
第二種情況:派生類中重寫了基類中的某個虛函數並且還有自己的虛函數。
如果在派生類中重寫了基類裏的虛函數,用派生類重寫後的虛函數代替基類裏的虛函數。
如果派生類定義了新的虛函數,按照聲明順序放在基類虛函數的後面。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "A::Funtest2" << endl;
}
virtual void Funtest3()
{
cout << "A::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "A::Funtest4" << endl;
}
};
class B :public A
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
virtual void Funtest5()
{
cout << "B::Funtest5" << endl;
}
};
typedef void(*PVTF)();//函數指針
void PrintVpt()//打印虛表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
多繼承
在這裏我把所有的可能性混合到一塊:就是說派生類中對基類A、B的虛函數都進行了重寫,並且還擁有自己的虛函數。
如果派生類中對基類A、B中的虛函數進行了重寫,分別替換基類A、B中的虛函數;
如果派生類中有自己的虛函數,按照聲明順序放在第一個基類(也就是A類)虛表中虛函數的後面。
多繼承中派生類不會重新生成表,而是直接將基類A、B的虛表直接繼承下來。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
};
class C :public A, public B
{
public:
virtual void Funtest1()
{
cout << "C::Funtest1" << endl;
}
virtual void Funtest2()
{
cout << "c::Funtest2" << endl;
}
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
};
typedef void(*PVTF)();//函數指針
void PrintVpt()//打印虛表
{
C c;
PVTF* pVtf = (PVTF*)(*(int*)&c);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
A a;
B &b = c;
pVtf = (PVTF*)*(int*)&b;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
PrintVpt();
}
菱形繼承(鑽石繼承)
這裏我列舉的這種情況是派生類中有自己的虛函數,繼承時沒有重寫虛函數,這樣舉例是爲了更能直觀的看到它的對象模型。因爲一旦有重寫,在對象模型中你會分不清它到底是派生類的還是基類的。
包含虛函數的菱形繼承,派生類的對象模型其實是這樣的,本例中沒有給出類中成員變量,加上成員變量(好理解點的話就理解爲功能)的話,模型是這樣的:
B1的虛表中包含了最初的基類A,以及派生類C的虛函數,B2的虛表中包含了最初的基類A的虛函數。同樣的,如果派生類中對基類B1、B2中的虛函數進行了重寫,分別替換基類B1、B2虛表中的虛函數,同理B1、B2中對A類的虛函數進行了重寫,也會替換。
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B1:public A
{
public:
virtual void Funtest2()
{
cout << "B1::Funtest2" << endl;
}
};
class B2 :public A
{
public:
virtual void Funtest3()
{
cout << "B2::Funtest3" << endl;
}
};
class C :public B1,public B2
{
public:
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
};
typedef void(*PVTF)();//函數指針
void PrintVpt()//打印虛表
{
C c;
PVTF* pVtf = (PVTF*)(*(int*)&c);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
B2 &b = c;
pVtf = (PVTF*)*(int*)&b;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(B1) << endl;
cout << sizeof(B2) << endl;
cout << sizeof(C) << endl;
PrintVpt();
}
虛擬繼承
單繼承(虛擬繼承)
對象模型應該是這樣的:
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
};
class B :virtual public A
{
public:
virtual void Funtest2()
{
cout << "B::Funtest2" << endl;
}
virtual void Funtest1()
{
cout << "B::Funtest1" << endl;
}
};
typedef void(*PVTF)();//函數指針
void PrintVpt()//打印虛表
{
B b;
PVTF* pVtf = (PVTF*)(*(int*)&b);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
A &a = b;
pVtf = (PVTF*)*((int*)&a);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
PrintVpt();
}
菱形虛擬繼承
class A
{
public:
virtual void Funtest1()
{
cout << "A::Funtest1" << endl;
}
int _a;
};
class B1 :virtual public A
{
public:
virtual void Funtest2()
{
cout << "B1::Funtest2" << endl;
}
int _b1;
};
class B2 :virtual public A
{
public:
virtual void Funtest3()
{
cout << "B2::Funtest3" << endl;
}
virtual void Funtest1()
{
cout << "B2::Funtest1" << endl;
}
int _b2;
};
class C :public B1, public B2
{
public:
virtual void Funtest3()
{
cout << "C::Funtest3" << endl;
}
virtual void Funtest4()
{
cout << "C::Funtest4" << endl;
}
int _c;
};
typedef void(*PVTF)();
void PrintVft()
{
C c;//創建派生類對象d
c._a = 1;
c._b1 = 2;
c._b2 = 3;
c._c = 4;
PVTF* pVtf= (PVTF*)*(int*)&c;
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
cout << endl << endl;
pVtf = (PVTF*)*((int*)&c + 3);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
cout << endl << endl;
pVtf = (PVTF*)*((int*)&c + 7);
while (*pVtf)
{
(*pVtf)();
pVtf++;
}
}
void test()
{
cout << sizeof(C) << endl << endl;
PrintVft();
}
對象模型:
在這裏總結一下:從包含虛函數的菱形虛擬繼承中我們可以看出來,它其實和不包含虛函數的菱形虛擬繼承對象結構差不了多少,只是派生類中的虛函數要寫到第一個基類裏面,在原有的偏移量表格地址上把虛表也繼承過來了。