c++是一門混合型編程語言,即支持面向對象有支持面向過程,其中又以面向對象爲主。c++的三大主要特性:「繼承」,「封裝」,「多態」中,又以「多態」最難以理解,本文將經過c的視角來詮釋c++的多態。ios
不一樣的指針類型,意味這對同一塊內存起始地址的不一樣解析方式,下面咱們舉一個栗子。c++
代碼 test4.c編程
#include <stdint.h> #include <string.h> #include <malloc.h> #include <stdio.h> int32_t g_int = 10; int main() { //申請5個字節大小的堆內存 void * p = malloc(5); //每一個字節的值設置爲90 memset(p, 90, 5); //聲明一個char指針pc指向分配的內存的起始地址 char * pc = (char *)p; //聲明一個int32_t指針pint指向分配的內存的起始地址 int32_t * pint = (int32_t *)p; //從起始地址開始取1個字節的內存去解析成char變量 if (*pc == 90) { printf("char yes\n"); } //從起始地址開始取4個字節的內存去解析成int32_t變量 if (*pint == 90 + (90 << 8) + (90 << 16) + (90 << 24)) { printf("int yes\n"); } return 0; }
運行結果編程語言
詳細圖解ide
至少應該包含函數符號,全局變量(若是文件中有定義全局變量)。函數
經過gcc和nm命令咱們能夠看到用上面的測試代碼生成的test4.o這個目標文件中包含有一個全局變量g_int,一個有定義的函數符號main,三個未定義的函數符號malloc,memset,puts。測試
命令執行結果編碼
代碼test5.cppspa
#include <iostream> using namespace std; void fun1() { cout << "fun1 call" << endl; } void fun1(int a) { cout << "fun1 a call" << endl; } int main() { fun1(); fun1(10); return 0; }
運行結果操作系統
經過重載(overload)的特性來實現,在編譯階段就決定要調用那個函數,故稱爲靜態多態。
c++編譯器在編譯代碼時,會對函數符號重簽名(c編譯器不會),當c++編譯器遇到重載調用時則直接調用重簽名後的函數,使用nm命令查看可執行文件的符號咱們看到兩個被重簽名的符號
#include <stdio.h> void _Z4fun1v() { printf("fun1 call\n"); } void _Z4fun1i(int a) { printf("fun1 a call\n"); } int main() { _Z4fun1v(); //對應以前的void fun1(); _Z4fun1i(10); //對應以前的void fun1(int a); return 0; }
代碼test6.cpp
#include <iostream> using namespace std; class Base { public: virtual void sleep() { cout << "Base sleep" << endl; } virtual void eat() { cout << "Base eat" << endl; } virtual void run() { cout << "Base run" << endl; } }; class Animal : public Base { public: size_t age; void sleep() { cout << "Animal sleep" << endl; } void eat() { cout << "Animal eat" << endl; } void run() { cout << "Animal run" << endl; } }; /* 定義一個函數指針類型,類型爲 void () (Animal * ); 用於指向虛函數sleep,eat,run; 這裏之因此多出一個Animal * 參數是由於c++類的非靜態成員函數, 編譯器會默認在參數列表開頭加入指向類指針的參數 */ typedef void (* pFun)(Animal * animal); int main() { Animal dargon; Animal dog; Base * pBase = &dargon; Base & pRe = dog; //經過基類的指針指向派生類對象來實現動態多態 pBase->sleep(); //經過基類的引用指向派生類對象來實現動態多態 pRe.sleep(); /* 取出Animal的虛表指針. (size_t *)&dargon -> dargon起始地址轉換爲size_t * *(size_t *)&dargon -> dargon起始地址開始取sizeof(size_t)個字節解析成size_t(虛表指針的值) (size_t *)*(size_t *)&dargon -> 把這個值轉換成size_t *類型 ps: size_t在32位機是4個字節,在64位機是8個字節,指針變量的大小和size_t的大小是一致的。 */ size_t * vptable_dargon = (size_t *)*(size_t *)&dargon; size_t * vptable_dog = (size_t *)*(size_t *)&dog; cout << "size_t size = " << sizeof(size_t) << endl; //一個類公用一個虛表指針 if (vptable_dog == vptable_dargon) { cout << "vptable value is equal" << endl; } //遍歷虛表指針 while (*vptable_dargon) { //取出每一個虛表函數 pFun fun = (pFun)(*vptable_dargon); //調用每一個虛表函數 fun(&dargon); vptable_dargon++; } return 0; }
運行結果
經過c++的重寫(override)的特性來實現,只有在運行時才知道真正調用是什麼那個函數,故稱爲動態多態。
c++爲有虛函數的每一個類添加了一個虛函數表(類的靜態變量),並在每一個類對象的起始地址處嵌入一個虛表指針指向它,再經過這個虛表指針來實現運行時的多態。
代碼test7.cpp
#include <stdio.h> #include <malloc.h> #include <string.h> //全局靜態的虛表指針,模擬類的靜態虛表指針 static size_t * pBaseVptable = NULL; static size_t * pAnimalVptable = NULL; struct Base { size_t * vptable; //模擬虛表指針 }; struct Animal { struct Base base; //模擬Animal繼承Base size_t age; }; typedef void (* pFun)(Base * pBase); void baseSleep(Base * pBase) { printf("Base sleep\n"); } void baseEat(Base * pBase) { printf("Base eat\n"); } void baseRun(Base * pBase) { printf("Base run\n"); } void animalSleep(Animal * pAnimal) { printf("Animal age[%d] sleep\n", pAnimal->age); } void animalEat(Animal * pAnimal) { printf("Animal age[%d] eat\n", pAnimal->age); } void animalRun(Animal * pAnimal) { printf("Animal age[%d] run\n", pAnimal->age); } //Base結構體初始化 void baseInit(Base * pBase) { pBase->vptable = pBaseVptable; } //Animal結構體初始化 void AnimalInit(Animal * pAnimal) { pAnimal->base.vptable = pAnimalVptable; } /* 虛表指針初始化 */ void vptableInit() { pBaseVptable = (size_t *)malloc(sizeof(size_t) * 4); pAnimalVptable = (size_t *)malloc(sizeof(size_t) * 4); memset(pBaseVptable, 0x0, sizeof(size_t) * 4); memset(pAnimalVptable, 0x0, sizeof(size_t) * 4); //Base類全局虛表初始化 pBaseVptable[0] = (size_t)&baseSleep; pBaseVptable[1] = (size_t)&baseEat; pBaseVptable[2] = (size_t)&baseRun; //Animal類全局虛表初始化 pAnimalVptable[0] = (size_t)&animalSleep; pAnimalVptable[1] = (size_t)&animalEat; pAnimalVptable[2] = (size_t)&animalRun; } void callVirtualFun(Base * pBase, int index) { pFun fun = (pFun)pBase->vptable[index]; fun(pBase); } int main() { //虛表初始化 vptableInit(); Base * pBase = NULL; Animal * pAnimal = (Animal *)malloc(sizeof(Animal)); //模擬對象初始化 AnimalInit(pAnimal); pAnimal->age = 99; //模擬基類指針指向派生類 pBase = (Base *)pAnimal; //模擬調用虛函數 callVirtualFun(pBase, 0); callVirtualFun(pBase, 1); callVirtualFun(pBase, 2); return 0; }
運行結果
操做系統
[root@iZ940zytujjZ ~]# uname -m -s
Linux x86_64
編譯器
[root@iZ940zytujjZ ~]# gcc --versiongcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)[root@iZ940zytujjZ ~]# g++ --versiong++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)