C++虚函数和多态

跟狄泰软件学院的唐老师学习C++已经有一段时间了,发现虚函数的概念一直不是很清楚,今天把唐老师的课程重新看了一下,先将相关知识点总结一下。

一、多态的概念

  多态值通过类的指针(引用)调用类的方法时,根据实际的对象决定调用函数的具体目标。也就是说同样的调用语句在实际运行时有多种不同的表现形态。

二、多态的实现方式

  C++ 直接支持多态的概念,通过使用virtual关键字对多态进行支持。被virtual声明的函数被子类重写后具有多态特性。被virtual声明的函数叫做虚函数。

三、示例一

#include <iostream>#include <string>using namespace std;class Parent
{public:    virtual void print()
    {        cout << "I'm Parent." << endl;
    }
};class Child : public Parent
{public:    void print()
    {        cout << "I'm Child." << endl;
    }
};void how_to_print(Parent* p)
{
    p->print();     // 展现多态的行为}int main()
{
    Parent p;
    Child c;

    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child.

    return 0;
}12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738

程序运行结果如下:

这里写图片描述

  上面代码中通过调用函数void how_to_print(Parent* p)来体现出多态性,虽然函数的参数都为Parent* p,但调用后的结果确不相同。下图为void how_to_print(Parent* p)函数执行流程:

这里写图片描述

四、C++多态的实现原理

  1.当类中声明虚函数时,编译器会在类中生成一个虚函数表; 
   
  2.虚函数表是一个存储成员函数地址的数据结构; 
   
  3.虚函数表是由编译器自动生成与维护的; 
   
  4.virtual成员函数地址会被编译器放入虚函数表; 
   
  5.存在虚函数表时,每个对象中都有一个指向虚函数表的指针,且放在类的最前面。

下图为虚函数调用流程:

这里写图片描述

下面通过一个示例来说明:

#include <iostream>#include <string>using namespace std;class Parent
{private:    int m_data;public:
    Parent() : m_data(10)
    {

    }    virtual void print()
    {        cout << "I'm Parent." << endl;
    }
};class Child : public Parent
{public:    void print()
    {        cout << "I'm Child." << endl;
    }
};struct Data {   // 模拟Child在内存中的分布
    int *p;    int data;
};typedef void (*Fun)();int main()
{
    Parent* c = new Child();

    Data* data = (Data*)c;    cout << "sizeof(Child): " << sizeof(Child) << endl;    cout << endl;    cout << "data->p: "<< data->p << endl;    cout << "*(data->p): " << *(data->p) << endl;    cout << "data->data:" << data->data << endl;    cout << endl;

    Fun fun = (Fun)(*(data->p));
    fun();    return 0;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758591234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859

程序运行结果如下图所示:

这里写图片描述

  1.sizeof(Child): 8:表明Child除了一个继承自父类Parent的int m_data;成员变量所占用4字节之外,还多出了4字节。这4字节便用来存放虚函数表; 
   
  2.data->p: 0x404318:虚函数表地址; 
   
  3.data->data:10:打印出的是m_data对应的值,更进一步证明虚函数表是存储在类的最前面,占用4字节; 
   
  4.*(data->p): 4204792:虚函数表中存储的内容,是对应虚函数的入口地址,即void print()的入口地址。通过下一句的打印I'm Child.即可证明。

五、构造函数和析构函数能否声明为虚函数

  1.构造函数不可能成为虚函数,因为在构造函数执行结束后,虚函数表指针才会被正确的初始化,所以在构造函数中不可能发生多态。 
   
  2.析构函数可以设计成虚函数。若发生继承关系时,父类的析构函数一定要实现为虚函数,否则若通过父类指针释放子类对象时无法调用子类的析构函数,会造成资源释放不完全。 
   
  3.析构函数中也不可能发生多态行为。因为在析构函数执行时,虚函数表指针已经被销毁,只能调用当前类中定义的版本。

  • 1


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章