c++封装继承多态
面向对象的三个基本特征
封装、继承、多态。其中,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用
封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
1. 实现继承是指使用基类的属性和方法而无需额外编码的能力;
2. 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
3. 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
多态
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,面向对象的核心,多态的目的则是为了接口重用。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
要熟悉掌握多态,就要了解是动态绑定还是静态绑定
先熟悉几个名次
- 静态类型:对象在声明时采用的类型,在编译期既已确定;
- 动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
- 静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
- 动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
class Base { ... }; class Derived: public Base { ... }; Derived d; Base *pb = &d; // implicitly convert Derived* => Base*
从上面的定义也可以看出,非虚函数一般都是静态绑定,而虚函数都是动态绑定(如此才可实现多态性)
#include <iostream> using namespace std; class Father { public: virtual void show1() { cout<<"this is Father virtual function"<<endl; } void show2() { cout<<"this is Father function"<<endl; } void show3() { cout<<"this is Father not inherit function"<<endl; } }; class Son:public Father { public: void show1() { cout<<"this is Son virtual function"<<endl; } void show2() { cout<<"this is Son function"<<endl; } }; int main() { Father *f=new Father();//f的静态类型是Father*,动态类型也是Father* Son *s=new Son();//s的静态类型是Son*,动态类型也是Son* Father *f1=s;//f1的静态类型是Father,动态类型是f1所指的对象s的类型 Son *s_null=NULL;//s_null的静态类型是s1,无动态类型 f1->show1();//virtual函数动态绑定,运行时根据函数所依赖的对象类型绑定 f1->show2();//因为show()不是virtual函数,在编译时绑定,无论f1指向谁,静态类型永远是Father //即使f1指向s,也调用自己的show()2 s->show1();//virtual函数,动态绑定,根据函数依赖的对象的类型,所以调用自己的show1() s->show2();//s的动静态类型都是Son*,所以调用自己的show() s->show3();/* *Son的内存空间存储情况为[Faher成员变量/函数][Son成员变量/函数]+[指向虚表的指针] *Son中不包含任何show3()成员函数的信息,所以到基类中寻找 * */ //s_null->show1();崩溃,因为virtual是运行时才能确定,但是依赖的对象类型是NULL,程序崩溃 s_null->show2();//没virtual,静态绑定,用对象声明时的类型, s_null->show3();//同上 ; }
如果show1()不是virtual函数,那么不论f1和s指向什么,对show1()的调用在编译时确定
同样的空指针也能够直接调用no-virtual函数而不报错(这也说明一定要做空指针检查啊!),因此静态绑定不能实现多态;
静态绑定和动态绑定的区别:
1. 静态绑定发生在编译期,动态绑定发生在运行期;
2. 对象的动态类型可以更改,但是静态类型无法更改;
3. 要想实现动态,必须使用动态绑定;
4. 在继承体系中只有虚函数使用的是动态绑定,其他的全部是静态绑定;
建议:
绝对不要重新定义继承而来的非虚(non-virtual)函数(《Effective C++ 第三版》条款36),因为这样导致函数调用由对象声明时的静态类型确定了,而和对象本身脱离了关系,没有多态,也这将给程序留下不可预知的隐患和莫名其妙的BUG;
动态绑定也即在virtual函数中,要注意默认参数的使用。当缺省参数和virtual函数一起使用的时候一定要谨慎,不然出了问题怕是很难排查
class E { public: ) { std::cout << "E::func()\t"<< i <<"\n"; } }; class F : public E { public: ) { std::cout << "F::func()\t" << i <<"\n"; } }; void test2() { F* pf = new F(); E* pe = pf; pf->func(); //F::func() 1 正常,就该如此; pe->func(); //F::func() 0 调用了子类的函数,却使用了基类中参数的默认值! }
请看《Effective C++ 第三版》 条款37。
建议:
绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定
参考:http://www.cnblogs.com/lizhenghn/p/3657717.html
https://www.zhihu.com/question/25572937
https://blog.csdn.net/hackbuteer1/article/details/7475622
最新文章
- iOS-性能优化3
- Android下常见动画
- 纯JSP实现简单登录跳转
- Linux vim 底下显示行号
- 初学java之大数处理
- Linux上的运行的jar包
- Linux经久不衰的应用程序
- 【学习笔记01】:hover为DIV添加鼠标悬停时改变颜色的效果
- php使用check box
- Eclipse TypeScript 安装
- 每天一个Linux命令(16)--which命令
- Windows下ActiveMQ的下载和启动
- 论文笔记:Auto-DeepLab: Hierarchical Neural Architecture Search for Semantic Image Segmentation
- HDU6187(对偶图生成树)
- sqlserver 数据简单查询
- saliency map [转]
- vue axios请求/响应拦截器
- 修改Nginx的header伪装服务器
- 《Wrox.Professional.Hadoop.Solutions》中文目录全稿
- 原来javascript 自带 encodeURI 和 decodeURI文 方法了
热门文章
- Kotlin------函数和代码注释
- Oracle like &#39;%...%&#39; 优化
- HDU 4745 Two Rabbits ★(最长回文子序列:区间DP)
- js排序算法05——快速排序
- Java复习5.面向对象
- 【网络编程】inet_addr、inet_ntoa、inet_aton、inet_ntop和inet_pton区分
- MySQL20个经典面试题
- C++面向对象高级编程(三)基础篇
- shell脚本实例一
- 软件测试模型---V模型、W模型、H模型、X模型