用word编辑得好好的,粘贴到这里,就乱成这样...
2.7:封装
封装是如何定义的呢?封装就是将数据和行为进行结合,形成一个有机整体。
封装的目的:增加安全性,简化编程。
客户程序员(类的使用者)只需要调用类暴露出来的外部接口,而不必知道类中各个接口具体是如何实现的。
2.8:继承
继承是一种机制,这种机制可以利用已有的数据类型来定义性的数据类型(由基类到派生类),所定义的新的数据类型不仅拥有新定义的成员,还同时拥有旧成员(继承自基类的成员)。
子父 | Public | protected | Private |
公有继承 | Public | Protected | 不可见 |
私有继承 | Private | Private | 不可见 |
保护继承 | Protected | Protected | 不可见 |
这张继承表格:
(1):基类的private部分,不管在哪种继承体系下,在派生类中都是不可见的。
(2):注意三种继承关系下,父类和子类的关系。
3:对C++中引用的理解
引用就是某一个变量的别名,对这个引用的操作和对变量的直接操作,效果是完全一样的。
- 声明一个引用的同时,要对其进行初始化。也就是说,要绑定要一个变量上。之后不可以修改,让这个引用又指向了其他的变量。保持:从一而终。
- 引用本身不占内存,仅仅是个别名而已,引用本身不是一种数据类型。
引用作为函数参数的特点:
- 传递引用给函数与传递指针的效果是一样的。
- 传递引用,在内存中并没有产生实参的副本。也就是说,调用某一个函数的时候,传递引用,则实参不需要拷贝一份,再传过去。而是直接把实参传过去,省去了拷贝产生副本的过程。从侧面来说,提高了效率,节省了空间。
- 引用比起指针来,更容易使用,语法,语意上也更加清晰。
常引用的目的:既想提高效率,又不愿意参数在函数体中被修改,所以采用常引用。
引用与多态的关系:
曾经测试过这样的关系,写过代码,用来实现表达的是:一个基类的引用可以指向其派生类的实例。
4:多态的作用
1) 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用。
2) 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性的正确使用。
5:什么情况下只能使用成员初始化列表,而不能用赋值?
1) 类中有const,reference成员变量,对他们进行初始化的时候,只能是在成员初始化列表中。为什么呢?const,reference在定义的时候,都必须初始化,以后都不可更改,正因为有这样的属性,若是在类的构造函数中对他们进行初始化,则就有被修改的可能。所以,对他们的初始化放在初始化列表中,在未进入构造函数的时候,就完成初始化工作。
2) 派生类构造函数调用基类的构造函数的时候都要使用初始化列表。
理解:派生类定义对象的时候,首先要调用基类的构造函数进行初始化,再是派生类自己的构造函数进行初始化工作。而在派生类的构造函数中,使用初始化列表的形式,去调用基类的构造函数,完成初始化操作。
面试宝典上一道题的分析:(多态)
代码
#include <iostream>using namespace std; class A{public: A(int data=0) { m_data=data; } int GetData() { return DoGetData(); } virtual int DoGetData() { return m_data; }protected: int m_data;}; class B: public A{public: B(int data=1) { m_data=data; } int DoGetData() { return m_data; }protected: int m_data;}; class C:public B{public: C(int data=2) { m_data=data; }protected: int m_data;}; int main(){ C c(10); cout<<c.GetData()<<endl; // 1 cout<<c.A::GetData()<<endl; // 1 cout<<c.B::GetData()<<endl; // 1 cout<<c.C::GetData()<<endl; // 1 cout<<c.DoGetData()<<endl; // 1 cout<<c.A::DoGetData()<<endl; // 0 cout<<c.B::DoGetData()<<endl; // 1 cout<<c.C::DoGetData()<<endl; // 1 return 0;}
分析:
c.GetData():这个动作,C的对象调用从基类继承下来的成员函数GetData(),此刻的GetData()还是属于A的,
为什么呢?因为,类的一般的成员函数,只有一份,系统只维护一份。接着这个GetData()函数里面又去调用
另外一个方法,而这个方法是虚方法,问题就来了--->到底调用哪个虚方法呢?这就是多态,此时,对象c,
会去根据这个对象的vptr,遍历C类的虚函数表,找到个虚方法(虚方法都放在虚函数表中)。所以,调用的是
B类的虚方法,因为C继承自B,而C类中未改写B类的这个虚方法,所以是B的虚方法。
c.A::GetData():这个动作,编译的时候静态绑定调用的是A::GetData(),明确说了,是A的成员函数,但里面
调用的却是一个虚方法,需要动态绑定。最终:结果和上面分析的一样。
c.B::GetData(),c.C::GetData():情形都和上面的一样。
c.DoGetData():这个动作,直接调用虚方法。对象c根据自己的vptr,去vtbl中找到这个虚方法来,调用它,就OK。
c.A::DoGetData()--->这个动作,是在编译的时候,就静态绑定了,调用哪一个虚函数,此时调用的是A的虚函数,
则要到A类的虚函数表中去找这么一个虚函数,为什么能够找到呢?因为,C c的时候,要先调用基类的构造函数
进行初始化,再是自己的构造函数。在调用基类构造函数的时候,就会为基类的虚函数表生成一个vptr(虚函数表
指针),所以,在c.A::DoGetData()这种情形下,能够顺利的通过基类的vptr,找到那个虚函数,调用它。
c.B::DoGetData(),c.C::DoGetData():情形和上面的一样。
6:重载,覆盖,隐藏的比较分析
重载:
- 同一个类中谈论重载才具有意义
- 函数名字相同,但参数个数不同,或者类型不同
- virtual,可有可无
覆盖:(子类覆盖基类)
- 不同类中,才谈论覆盖;在父类和子类中才存在这种关系
- 函数名字相同,参数相同
- 最重要的一点:必须是virtual函数,只有虚函数存在的前提下,才谈论覆盖的问题。
隐藏:
这个比较生疏,接触少些。
- 也是在不同类中,基类和派生类中
- 派生类和基类有同名函数,但参数不同,不管有无virtual,基类的这个函数将会被隐藏。
- 派生类和基类有同名函数,且参数相同,但不是virtual,基类函数被屏蔽。
原文链接: https://www.cnblogs.com/marrywindy/archive/2010/08/18/1802135.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/13980
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!