C++ 虚函数与虚函数的工作原理 绝地灬酷狼 2022-12-11 13:45 117阅读 0赞 ### 文章目录 ### * * * 虚函数 * * 虚析构函数 * 虚函数的工作原理 * 获取对象的虚函数表地址 ### 虚函数 ### 给类函数声明`virtual`关键字,将该函数解释为虚函数。基类定义的虚函数表明子类可以继承并实现该虚函数。虚函数的最大作用为:可以通过基类指针或引用来接收一个子类指针或者引用,并通过基类指针或引用来调用虚函数来实现相同的动作不同的实现方式,达到多态的目的。 例子代码如下: #include <iostream> #include <string> using namespace std; class BaseClass { public: virtual ~BaseClass(); //virtual void showme() { //虚函数 void showme() { //普通函数 std::cout << "父类shome()" << std::endl; }; }; class DeriveClass : public BaseClass { public: void showme() { std::cout << "子类shome()" << std::endl; } }; int main() { DeriveClass dc = DeriveClass(); BaseClass *bc = &dc; bc->showme(); return 0; } 上述代码如果将`BaseClass::showme()`定义为普通函数,那么虽然使用的BaseClass \*bc接收的DeriveClass的指针,得到的结果依然是`BaseClass::showme()`。 如果将`BaseClass::showme()`定义为虚函数,那么BaseClass \*bc接收的DeriveClass的指针,将会根据对象来判断,最终会调用`DeriveClass::showme()`。 这里涉及到静态联编和动态联编。 如果一个类的函数未声明`virtual`,那么在调用该函数处,编译器根据调用函数的类型进行静态联编,也就是在编译期间就已经定好调用哪个地址的函数。 如果一个类的函数通过`virtual`声明为了虚函数,那么该函数调用处,无法在编译期确定调用基类的函数还是派生类的函数,所以在调用函数处加入动态函数调用的代码。在程序运行时根据对象的虚函数表来查找最终调用的函数,并调用它。 #### 虚析构函数 #### 如果一个类将被作为基类,那么该类应该将析构函数声明为虚函数。因为只有这样,才能确保派生类指针赋值给基类指针时,基类和派生类的释放顺序是正确的。 #include <iostream> #include <string> using namespace std; class BaseClass { public: virtual ~BaseClass(){ std::cout << "释放父类" << std::endl; } }; class DeriveClass : public BaseClass { public: ~DeriveClass(){ std::cout << "释放子类" << std::endl; } }; int main() { BaseClass *bc = new DeriveClass(); delete bc; return 0; } 如果`BaseClass::~BaseClass()`未定义为虚函数,那么delete bc的动作不会调用`DeriveClass::~DeriveClass()`,只会调用父类的`BaseClass::~BaseClass()`。 只有基类的析构函数定义为虚函数:`virtual ~BaseClass()` 那么在多态发生时,才会先调用派生类的析构函数,然后调用基类的析构函数。 ### 虚函数的工作原理 ### 对于虚函数的处理,编译器会为每个对象开始处添加一个隐藏成员,来保存一个指向函数地址数组的指针。该函数地址数组叫虚函数表(virtualfunctiontable,vtbl),虚函数表里面存放的每个元素都是派生类与基类的每个虚函数的地址。 基类对象包含一个指针,该指针指向存放了基类声明的所有虚函数的虚函数表。派生类对象也包含一个指针指向了派生类的虚函数表,但是派生类的虚函数表从基类继承复制过来的,如果派生类重新定义了基类的虚函数,那么派生类的虚函数表中对应的虚函数地址就是派生类的虚函数地址;如果没有重新定义基类的虚函数,那么派生类的虚函数表存放的还是指向基类的虚函数地址。 #include <iostream> #include <string> using namespace std; class BaseClass { public: virtual void one() { std::cout << "父类one()" << std::endl; }; virtual void two() { std::cout << "父类two()" << std::endl; }; }; class DeriveClass : public BaseClass { public: virtual void one() { std::cout << "子类shome()" << std::endl; } virtual void three(){ std::cout << "子类three()" << std::endl; } }; int main() { DeriveClass dc = DeriveClass(); BaseClass *bc = &dc; bc->one(); bc->two(); dc.three(); return 0; } 如上代码DeriveClass继承了BaseClass并重新定义了one虚函数,以及新增了three的虚函数。 基类和派生类对应的虚函数表分别如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM2MzI3NTU_size_16_color_FFFFFF_t_70_pic_center] 从上图看出,DeriveClass的虚函数表one()和three()指向的是自己的函数地址,two()指向的是父类的函数地址。所以运行上面代码得到如下结果: 子类shome() 父类two() 子类three() ### 获取对象的虚函数表地址 ### 看到了知乎上面的一篇文章,确实很棒。 源链接:[https://www.zhihu.com/question/29256578/answer/43725188][https_www.zhihu.com_question_29256578_answer_43725188] #include <iostream> using namespace std; class animal { protected: int age_; animal(int age): age_(age) { } public: virtual void print_age(void) = 0; virtual void print_kind() = 0; virtual void print_status() = 0; }; class dog : public animal { public: dog(): animal(2) { } ~dog() { } virtual void print_age(void) { cout << "Woof, my age = " << age_ << endl; } virtual void print_kind() { cout << "I'm a dog" << endl; } virtual void print_status() { cout << "I'm barking" << endl; } }; class cat : public animal { public: cat(): animal(1) { } ~cat() { } virtual void print_age(void) { cout << "Meow, my age = " << age_ << endl; } virtual void print_kind() { cout << "I'm a cat" << endl; } virtual void print_status() { cout << "I'm sleeping" << endl; } }; void print_random_message(void* something) { cout << "I'm crazy" << endl; } int main(void) { cat kitty; dog puppy; animal* pa = &kitty; intptr_t* cat_vptr = *((intptr_t**)(&kitty)); intptr_t* dog_vptr = *((intptr_t**)(&puppy)); intptr_t fake_vtable[] = { dog_vptr[0], // for dog::print_age cat_vptr[1], // for cat::print_kind (intptr_t) print_random_message }; *((intptr_t**) pa) = fake_vtable; pa->print_age(); // Woof, my age = 1 pa->print_kind(); // I'm a cat pa->print_status(); // I'm crazy return 0; } 其他参考资料:[https://blog.csdn.net/u012218838/article/details/79443655][https_blog.csdn.net_u012218838_article_details_79443655] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM2MzI3NTU_size_16_color_FFFFFF_t_70_pic_center]: /images/20221123/658a4dfbf6cc486fb8ddbfdb6ee3e14a.png [https_www.zhihu.com_question_29256578_answer_43725188]: https://www.zhihu.com/question/29256578/answer/43725188 [https_blog.csdn.net_u012218838_article_details_79443655]: https://blog.csdn.net/u012218838/article/details/79443655
还没有评论,来说两句吧...