C++ Primer学习笔记(14)——虚函数的实现机制、纯虚函数
2015-07-08 20:46
501 查看
在行文之前查阅了相关书籍,参考了一些别人的博客,在这里谢谢大家的分享!希望自己和大家在学习语言的道路上渐行渐远,一直走下去~~~
上一篇文章中说道,C++ 的三个基本特质是 封装、继承、多态。
多态性是将接口与实现进行分离。用形象的语言解释就是实现已共同的方法,但因个体差异而采用不同的策略。
多态包括静多态和动多态,分别在编译和运行过程中实现。而动多态是由虚函数来实现的,其实现机制体现了C++的神秘性。
两个重要的关键词揭示了它的神秘面纱 : 虚表、虚指针。每个类用了一个虚表,每个类对象用了一个虚指针。
具体演示如下:
因为 A 有 virtual void f( ) ,和 g( ),所以编译器为 A 类准备了一个虚表 vtableA,内容如下:
B 继承了A,所以里面也有继承于A的虚函数,故B也有一个虚表vtableB:
PS:因为B:: g是重写了的,所以B的虚表的g放的是B::g的入口地址,而f 还是A:: g 的入口地址。
当执行B bt; 定义类对象的时候,编译器分配空间时,除了 A 的int a 成员 , B的 int b 成员,还分配了指向B的虚表vtableB 的指针vptr , 对象bt 的布局如下:
其中,虚表指针总是在最前面的。
当执行下面的语句时:
pa ->g( ) 实际上是由 指针vptr指向B的虚表vtableB,然后去里面寻找g()函数。可以看到,vtableB里面是 B:: g( ),即B 自己的g( ),而不是A 的g( )。
这就是多态。
总结:
要知道 虚函数实现多态的机制,谨记虚表、虚指针。能够列出父类和子类的虚表,以及子类对象的虚指针,就不难理解这个机制了。
假如定义的虚函数为:
那么如下就是纯虚函数:
存在纯虚函数的类称为 抽象类,是一个单纯地接口,抽象类本身实例化,即不能生成对象,只能也必须在派生类中去实例化。
什么情况下使用纯虚函数呢?
当想在基类中抽象出一种方法,且该基类只能被继承,不能实例化
这个方法必须在派生类中实现
针对上述两点分别举例说明:
1) 抽象类不能实例化
例如:定义一个形状的类(Cshape),但凡是形状我们都要求能显示自己,所以定义一个类如下:
我们不想将这个类实例化,首先会想到将show( )这个函数的函数体 { } 删除,改为 virtual show( );
这时如果尝试实例化 Cshape shape; 这样其实是能够通过编译的,只能在连接的时候报错。
那么如何能够在编译的时候就报错呢?
~~用纯虚函数:
2) 该方法必须在派生类中实现
如上面的例子,show( ) 必须在每一个子类中实现,即在子类中重新定义自己的 show( ) 函数,若没有定义,编译时就会报错:
在编译的时候就会报错!!!
~~~~
这是不是一个预防在派生类中实现基类的方法? (^__^)
上一篇文章中说道,C++ 的三个基本特质是 封装、继承、多态。
多态性是将接口与实现进行分离。用形象的语言解释就是实现已共同的方法,但因个体差异而采用不同的策略。
多态包括静多态和动多态,分别在编译和运行过程中实现。而动多态是由虚函数来实现的,其实现机制体现了C++的神秘性。
1.虚函数的实现机制
虚函数是那些以 virtual 关键字修饰的成员函数,是用来实现多态的。两个重要的关键词揭示了它的神秘面纱 : 虚表、虚指针。每个类用了一个虚表,每个类对象用了一个虚指针。
具体演示如下:
class A { public: virtual void f(); virtual void g(); private: int a; }; class B : public A { public: void g(); // 覆盖父类A中的虚函数g() private: int b; };
因为 A 有 virtual void f( ) ,和 g( ),所以编译器为 A 类准备了一个虚表 vtableA,内容如下:
B 继承了A,所以里面也有继承于A的虚函数,故B也有一个虚表vtableB:
PS:因为B:: g是重写了的,所以B的虚表的g放的是B::g的入口地址,而f 还是A:: g 的入口地址。
当执行B bt; 定义类对象的时候,编译器分配空间时,除了 A 的int a 成员 , B的 int b 成员,还分配了指向B的虚表vtableB 的指针vptr , 对象bt 的布局如下:
其中,虚表指针总是在最前面的。
当执行下面的语句时:
A *pa = &bt; //一个A 类型的指针,指向B类型的对象bt
pa ->g( ) 实际上是由 指针vptr指向B的虚表vtableB,然后去里面寻找g()函数。可以看到,vtableB里面是 B:: g( ),即B 自己的g( ),而不是A 的g( )。
这就是多态。
总结:
要知道 虚函数实现多态的机制,谨记虚表、虚指针。能够列出父类和子类的虚表,以及子类对象的虚指针,就不难理解这个机制了。
2. 纯虚函数(pure virtual function)
很多时候,定义一个类的对象是没有意义的,例如,动物这个类,由它可以派生出大象、狮子、猴子等子类,但动物本身生成对象是没有实际意义的。为了解决这个问题,提出了纯虚函数的概念。假如定义的虚函数为:
virtual void animals(string& name, int age){}
那么如下就是纯虚函数:
virtual void animals(string& name, int age) = 0; // 函数体直接为0 的虚函数
存在纯虚函数的类称为 抽象类,是一个单纯地接口,抽象类本身实例化,即不能生成对象,只能也必须在派生类中去实例化。
什么情况下使用纯虚函数呢?
当想在基类中抽象出一种方法,且该基类只能被继承,不能实例化
这个方法必须在派生类中实现
针对上述两点分别举例说明:
1) 抽象类不能实例化
例如:定义一个形状的类(Cshape),但凡是形状我们都要求能显示自己,所以定义一个类如下:
class Cshape { virtual show(){} };
我们不想将这个类实例化,首先会想到将show( )这个函数的函数体 { } 删除,改为 virtual show( );
这时如果尝试实例化 Cshape shape; 这样其实是能够通过编译的,只能在连接的时候报错。
那么如何能够在编译的时候就报错呢?
~~用纯虚函数:
class Cshape { virtual show() = 0; // 纯虚函数不能实例化 };
2) 该方法必须在派生类中实现
如上面的例子,show( ) 必须在每一个子类中实现,即在子类中重新定义自己的 show( ) 函数,若没有定义,编译时就会报错:
// 定义Cshape 的派生类,忘了定义show() class CPoint : public Cshape { public: void msg() { cout << " 这是一个点。" << endl; } private: float x; float y; }; // 下面实例化 CPoint 类的时候就会报错!!! CPoint point1;
在编译的时候就会报错!!!
~~~~
这是不是一个预防在派生类中实现基类的方法? (^__^)
相关文章推荐
- c语言函数指针
- cout的输出格式初探
- 错排问题--十二金钗
- c语言:typedef + struct + 指针
- sizeof和strlen
- 根据输入文件名确定输出文件名
- C++中的头文件和源文件
- LeetCode-Subsets-解题报告
- LeetCode-Binary Tree Level Order Traversal-解题报告
- C++ 之RTTI
- LeetCode-Rotate Array-解题报告
- LeetCode-Reverse Bits-解题报告
- LeetCode-Number of 1 Bits-解题报告
- LeetCode-Binary Tree Right Side View-解题报告
- LeetCode-Number of Islands-解题报告
- LeetCode-Bitwise AND of Numbers Range-解题报告
- 自学笔记-C语言复习2015年7月8日
- LeetCode-Remove Linked List Elements-解题报道
- LeetCode-Count Primes-解题报告
- LeetCode-Reverse Linked List-解题报告