跟狄泰软件学院的唐老师学习C++已经有一段时间了,发现虚函数的概念一直不是很清楚,今天把唐老师的课程重新看了一下,先将相关知识点总结一下。
一、多态的概念
多态值通过类的指针(引用)调用类的方法时,根据实际的对象决定调用函数的具体目标。也就是说同样的调用语句在实际运行时有多种不同的表现形态。
二、多态的实现方式
C++ 直接支持多态的概念,通过使用virtual关键字对多态进行支持。被virtual声明的函数被子类重写后具有多态特性。被virtual声明的函数叫做虚函数。
三、示例一#include #include using namespace std;class Parent
{public: virtual void print()
{ cout <
}
};class Child : public Parent
{public: void print()
{ cout <
}
};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 #include using namespace std;class Parent
{private: int m_data;public:
Parent() : m_data(10)
{
} virtual void print()
{ cout <
}
};class Child : public Parent
{public: void print()
{ cout <
}
};struct Data { // 模拟Child在内存中的分布
int *p; int data;
};typedef void (*Fun)();int main()
{
Parent* c = new Child();
Data* data = (Data*)c; cout <p: "<p <p): " <p) <data:" <data <
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踩