借著這次學校的生產實習來回顧下C++的多態,這裡討論下C++的多態以及實現原理。我都是在QT下使用C++的,就直接在QT下進行演示了
面向對象語言中最核心的幾個理念就是:封裝、繼承、多態,其中我感覺多態是真正的核心,第一第二個只是它的輔助。同時多態又是不容易懂的,所以在這就簡單的介紹下啦(雖然我也懂得不多,呵呵)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; p.getAge(); VellBibi v; v.getAge(); Person *pPtr = &v; pPtr->getAge(); return a.exec(); }
VellBibi
類繼承了Person
類,VellBibi
裡重載了Person
的getAge()
方法,這樣就實現了靜態的多態,它的實現過程是在編譯期間的。這種多態存在硬傷,就是當使用父類指針指向子類對象時,訪問的是父類的東西,而不是子類的。這個算是C++的一個特性,在java裡面就沒有這個情況,因為java直接就是是動態聯編,java的多態裡面就不存在靜態聯編
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} virtual void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} virtual void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; p.getAge(); VellBibi v; v.getAge(); Person *pPtr = &v; pPtr->getAge(); return a.exec(); }
這個小程序和上一個一樣,唯一變得就是在
Person
的getAge()
方法前加了一個virtual
關鍵字,VellBibi
可加可不加,但最好加上。在C++中virtual
關鍵字就是用來聲明虛函數的,所謂虛函數就是虛的函數嘛,呵呵,就是這個函數不是在編譯期間就確定下來了,而是在運行期間動態指定的。這就是導致這個小程序和上個小程序的運行結果不同的原因,當使用父類指針指向子類對象時,調用父類虛函數時系統會自動尋找到子類對象的函數。接下來介紹下C++是怎麼實現這個動態指定過程的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; p.getAge(); VellBibi v; v.getAge(); Person *pPtr = &v; pPtr->getAge(); qDebug() << "PersonSize: " << sizeof(p); qDebug() << "VellBibiSize: " << sizeof(v); return a.exec(); }
這個程序只是在第一個程序上加了
sizeof()
,看出來了神馬?Person
是4個字節,也就是int age;
的字節數;VellBibi
是8個字節,其實就是Person
的字節數加上VellBibi
的int age;
的字節數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} virtual void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} virtual void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; p.getAge(); VellBibi v; v.getAge(); Person *pPtr = &v; pPtr->getAge(); qDebug() << "PersonSize: " << sizeof(p); qDebug() << "VellBibiSize: " << sizeof(v); return a.exec(); }
這個程序只是在第二個程序上加了
sizeof()
,這時會發現Person
變8個字節了,為神馬呢?這是兩個int型的大小啊,why?好了不賣關子了,這是靜態Person
的大小加上一個指針的大小,那哪來的指針呢?在Person
裡面也沒有定義啊!呵呵,這是C++編譯器自動加上的,加上用來動態指定的,只要存在virtual
關鍵字的類最上面都是有一個這樣的指針,指向一個vtable
虛擬表,裡面記錄著這個類所有包含的虛函數地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} virtual void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} virtual void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; VellBibi v; int *pfv = (int *)&p; int *vfv = (int *)&v; qDebug() << "PersonFirstValue: " << *pfv; qDebug() << "VellBibiFirstValue: " << *vfv; qDebug() << "PersonSecondValue: " << *(pfv + 1); qDebug() << "VellBibiSecondValue: " << *(vfv + 1); qDebug() << "PersonThirdValue: " << *(pfv + 2); qDebug() << "VellBibiSThirdValue: " << *(vfv + 2); int *pt = (int *)*pfv; int *vt = (int *)*vfv; qDebug() << "PersonVTableFirstValue: " << *pt; qDebug() << "VellBibiVTableFirstValue: " << *vt; qDebug() << "PersonVTableSecondValue: " << *(pt + 1); qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1); return a.exec(); }
將對象第地址強制轉換成int型指針來探尋對象內部數據的情況。前兩行是對象的頭地址的值,很明顯這是一個指針;3、4行是第二個值,都是
Person
的int age;
變量; 5行是一個隨機值,說明Person
對象裡面其實就只有一個int age;
變量而已,其實在C++中類的實現也就是一個C語言的struct而已。再將頭地址指向的vtable
裡的值取出來看看,7、8行就是各自vtable
的第一個值,可以看出還是一個指針,指向的肯定是代碼段相對應的函數啦~這兩個指針指向了不同的函數,這也就是動態聯編啦~當然還需要一個程序來說明,等會再說;再看看9、10行,都是0,也就是NULL
很好理解,是不是似曾相識啊,沒錯就是字符串裡的結束符啦!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} virtual void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int age; VellBibi() : age(21){} virtual void getAge() { qDebug() << "VellBibi: " << age; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; VellBibi v; Person *vp = &v; int *pfv = (int *)&p; int *vfv = (int *)vp; qDebug() << "PersonFirstValue: " << *pfv; qDebug() << "VellBibiFirstValue: " << *vfv; qDebug(); qDebug() << "PersonSecondValue: " << *(pfv + 1); qDebug() << "VellBibiSecondValue: " << *(vfv + 1); qDebug(); qDebug() << "PersonThirdValue: " << *(pfv + 2); qDebug() << "VellBibiSThirdValue: " << *(vfv + 2); qDebug(); int *pt = (int *)*pfv; int *vt = (int *)*vfv; qDebug() << "PersonVTableFirstValue: " << *pt; qDebug() << "VellBibiVTableFirstValue: " << *vt; qDebug(); qDebug() << "PersonVTableSecondValue: " << *(pt + 1); qDebug() << "VellBibiVTableSecondValue: " << *(vt + 1); return a.exec(); }
這個應該很好理解了,只是定義了一個
Person
的指針vp
,指向了VellBibi
的對象v
,然後將&v
換成了vp
,其他的都沒變;也就是使用父類指針指向子類對象,結果可以看出和第五個程序是一樣的,說明了C++的動態聯編。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include <QCoreApplication> #include <QDebug> class Person { public : int age; Person() : age(10){} virtual void getAge() { qDebug() << "Person: " << age; } }; class VellBibi : public Person { public : int suiShu; VellBibi() : suiShu(21){} virtual void getAge() { qDebug() << "VellBibi: " << suiShu; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Person p; VellBibi v; Person *pptr = &p; int *pp = (int *)&p; int *vp = (int *)&v; *pp = *vp; pptr->getAge(); return a.exec(); }
結果可以看出該打印的
suiShu
變隨機數了;我這裡使用了點小計倆,我先將對象的頭地址當做int看待,然後將VellBibi
對象的頭地址賦值給了Person
對象,最後使用Person
對象的指針去調它自己的函數,會發現出錯了。上內存分析圖吧:
繼續分析,
Person
對象的vtable
指針指向了VellBibi
的vtable
,當使用指針去訪問時虛函數時就會做動態聯編,Person
對象會找到VellBibi
的vtable
然後找到了VellBibi
的getAge()
,而VellBibi
的getAge()
回去調它自己的變量也就是suishu
變量,這下就出問題了,Person
對象哪來的suishu
變量?所以就系統就在那個不是Person
對象內容的地方取了個值,當然這是隨機的啦
寫這篇帖子是為了明天實習的每人一講啦~不過更是一種分享,我是從別人那得到的這份知識,想更好的讓更多的人去收獲這份知識,這就是分享的意義;當得知我做的努力給別人帶來了幫助,我會非常高興,這就是分享的樂趣。一起分享一起學習,Come on~~~
原文地址: http://vview.ml/2014/07/20/polymorphisn.html
written by Vell Bibi posted at VBlog
如果對指針和內存分配有一定理解的話,多態我個人覺得是個非常簡單的問題。
一個類最多只允許從一個類中派生;不允許從兩個或者更多的類中派生。
在C#中的繼承符合下列規則:
??繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。
??派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。
??構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
??派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
??類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現類可以展示出多態性。
1.覆蓋
在類的成員聲明中,可以聲明與繼承而來的成員同名的成員。
2.多態(Polymorphism)
在面向對象的系統中,多態性是一個非常重要的概念,它允許客戶對一個對象進行操作,由對象來完一系列的動作,具體實現哪個動作,如何實現由系統負責解釋。
在C#中,多態性是指同一操作作用於不同的類的實例,不同的類將進行不同的解釋,最後產生不同的執行結果。C#支持兩種類型的多態性:
??編譯時的多態性
編譯時的多態性是通過重載來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數,返回的類型等信息決定實現何種操作。
??運行時的多態性
運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何種操作。C#中,運行時的多態性通過虛成員實現。
編譯時的多態提供了運行速度快的特點,而運行時的多態性則帶來了高度靈活和抽象的特點。