标签:
C++封装继承多态总结
面向对象的三个基本特征
面向对象的三个基本特征是:封装、继承、多态。当中,封装能够隐藏实现细节,使得代码模块化;继承能够扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现还有一个目的——接口重用!
封装
什么是封装?
封装能够隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的訪问仅仅能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界能够被描绘成一系列全然自治、封装的对象,这些对象通过一个受保护的接口訪问其它对象。在面向对象编程上可理解为:把客观事物封装成抽象的类,而且类能够把自己的数据和方法仅仅让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
什么是继承?
继承是指这样一种能力:它能够使用现有类的全部功能,并在无需又一次编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。
通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。要实现继承,能够通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些 OOP 语言中,一个子类能够继承多个基类。可是普通情况下,一个子类仅仅能有一个基类,要实现多重继承,能够通过多级继承来实现。
继承的实现方式?
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
1. 实现继承是指使用基类的属性和方法而无需额外编码的能力;
2. 接口继承是指仅使用属性和方法的名称、可是子类必须提供实现的能力;
3. 可视继承是指子窗口(类)使用基窗口(类)的外观和实现代码的能力。
多态
什么是多态?
多态性(polymorphisn)是同意你将父对象设置成为和一个或很多其它的他的子对象相等的技术,赋值之后,父对象就能够依据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:同意将子类类型的指针赋值给父类类型的指针。
样例:(2012某**软件公司笔试题)
请按顺序写出以下代码的输出结果:
答案:call child func
call ~child
call ~base
多态的实现方式分析?
实现多态,有二种方式,覆盖,重载。覆盖:是指子类又一次定义父类的虚函数的做法。重载:是指同意存在多个同名函数,而这些函数的參数表不同(也许參数个数不同,也许參数类型不同,也许两者都不同)。
分析:
“重载”是指在同一个类中同样的返回类型和方法名,可是參数的个数和类型能够不同
“覆盖\重写”是在不同的类中。
事实上,重载的概念并不属于“面向对象编程”,重载的实现是:编译器依据函数不同的參数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这种)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这种:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类又一次定义了父类的虚函数后,父类指针依据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这种函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这种函数地址是在执行期绑定的(晚邦定)。结论就是:重载仅仅是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,假设它不是晚邦定,它就不是多态。”
C++多态机制的实现:
该部分转自:http://blog.chinaunix.net/uid-7396260-id-2056657.html
1、c++实现多态的方法
面向对象有了一个重要的概念就是对象的实例,对象的实例代表一个详细的对象,故其肯定有一个数据结构保存这实例的数据,这一数据包含对象成员变量,假设对象有虚函数方法或存在虚继承的话,则还有对应的虚函数或虚表指针,其它函数指针不包含。
虚函数在c++中的实现机制就是用虚表和虚指针,可是详细是如何的呢?从more effecive c++当中一篇文章里面能够知道:是每一个类用了一个虚表,每一个类的对象用了一个虚指针。要讲虚函数机制,必须讲继承,由于仅仅有继承才有虚函数的动态绑定功能,先讲下c++继承对象实例内存分配基础知识:
从more effecive c++当中一篇文章里面能够知道:是每一个类用了一个虚表,每一个类的对象用了一个虚指针。详细的使用方法例如以下:
class A
{public:
virtual void f();
virtual void g();
private:
int a
};
class B : public A
{
public:
void g();
private:
int b;
};
//A,B的实现省略
由于A有virtual void f(),和g(),所以编译器为A类准备了一个虚表vtableA,内容例如以下:
A::f 的地址 |
A::g 的地址 |
B由于继承了A,所以编译器也为B准备了一个虚表vtableB,内容例如以下:
A::f 的地址 |
B::g 的地址 |
注意:由于B::g是重写了的,所以B的虚表的g放的是B::g的入口地址,可是f是从上面的A继承下来的,所以f的地址是A::f的入口地址。然后某处有语句 B bB;的时候,编译器分配空间时,除了A的int a,B的成员int b;以外,还分配了一个虚指针vptr,指向B的虚表vtableB,bB的布局例如以下:
vptr : 指向B的虚表vtableB |
int a: 继承A的成员 |
int b: B成员 |
当例如以下语句的时候:
A *pa = &bB;
pa的结构就是A的布局(就是说用pa仅仅能訪问的到bB对象的前两项,訪问不到第三项int b)
那么pa->g()中,编译器知道的是,g是一个声明为virtual的成员函数,并且其入口地址放在表格(不管是vtalbeA表还是vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:call *(pa->vptr)[1](C语言的数组索引从0開始哈~)。
这一项放的是B::g()的入口地址,则就实现了多态。(注意bB的vptr指向的是B的虚表vtableB)
另外要注意的是,如上的实现并非唯一的,C++标准仅仅要求用这样的机制实现多态,至于虚指针vptr究竟放在一个对象布局的哪里,标准没有要求,每一个编译器自己决定。我以上的结果是依据g++ 4.3.4经过反汇编分析出来的。
2、两种多态实现机制及其优缺点
除了c++的这样的多态的实现机制之外,还有第二种实现机制,也是查表,只是是按名称查表,是smalltalk等语言的实现机制。这两种方法的优缺点例如以下:
(1)、依照绝对位置查表,这样的方法因为编译阶段已经做好了索引和表项(如上面的call *(pa->vptr[1]) ),所以执行速度比較快;缺点是:当A的virtual成员比較多(比方1000个),而B重写的成员比較少(比方2个),这样的时候,B的vtableB的剩下的998个表项都是放A中的virtual成员函数的指针,假设这个派生体系比較大的时候,就浪费了非常多的空间。
比方:GUI库,以MFC库为例,MFC有非常多类,都是一个继承体系;并且非常多时候每一个类仅仅是1、2个成员函数须要在派生类重写,假设用C++的虚函数机制,每一个类有一个虚表,每一个表里面有大量的反复,就会造成空间利用率不高。于是MFC的消息映射机制不用虚函数,而用另外一种方法来实现多态,那就是:
(2)、依照函数名称查表,这样的方案能够避免如上的问题;可是因为要比較名称,有时候要遍历全部的继承结构,时间效率性能不是非常高。(关于MFC的消息映射的实现,看下一篇文章)
3、总结:
假设继承体系的基类的virtual成员不多,并且在派生类要重写的部分占了当中的大多数时候,用C++的虚函数机制是比較好的;可是假设继承体系的基类的virtual成员非常多,或者是继承体系比較庞大的时候,并且派生类中须要重写的部分比較少,那就用名称查找表,这样效率会高一些,非常多的GUI库都是这种,比方MFC,QT
PS 事实上,自从计算机出现之后,时间和空间就成了永恒的主题,由于两者在98%的情况下都无法协调,此长彼消;这个就是计算机科学中的根本瓶颈之所在。软件科学和算法的发展,就看能不能突破这对时空权衡了。呵呵
何止计算机科学如此,整个宇宙又何尝不是如此呢?最主要的宇宙之谜,还是时间和空间~
标签:
原文地址:http://www.cnblogs.com/gcczhongduan/p/4272089.html