跟狄泰软件学院的唐老师学习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
踩