C++回顾复习(续1)

用word编辑得好好的,粘贴到这里,就乱成这样...

2.7:封装

封装是如何定义的呢?封装就是将数据和行为进行结合,形成一个有机整体。

封装的目的:增加安全性,简化编程。

客户程序员(类的使用者)只需要调用类暴露出来的外部接口,而不必知道类中各个接口具体是如何实现的。

2.8:继承

继承是一种机制,这种机制可以利用已有的数据类型来定义性的数据类型(由基类到派生类),所定义的新的数据类型不仅拥有新定义的成员,还同时拥有旧成员(继承自基类的成员)。

子父 Public protected Private
公有继承 Public Protected 不可见
私有继承 Private Private 不可见
保护继承 Protected Protected 不可见

这张继承表格:

(1):基类的private部分,不管在哪种继承体系下,在派生类中都是不可见的。

(2):注意三种继承关系下,父类和子类的关系。

3:对C++中引用的理解

引用就是某一个变量的别名,对这个引用的操作和对变量的直接操作,效果是完全一样的。

  1. 声明一个引用的同时,要对其进行初始化。也就是说,要绑定要一个变量上。之后不可以修改,让这个引用又指向了其他的变量。保持:从一而终。
  2. 引用本身不占内存,仅仅是个别名而已,引用本身不是一种数据类型。

引用作为函数参数的特点:

  1. 传递引用给函数与传递指针的效果是一样的。
  2. 传递引用,在内存中并没有产生实参的副本。也就是说,调用某一个函数的时候,传递引用,则实参不需要拷贝一份,再传过去。而是直接把实参传过去,省去了拷贝产生副本的过程。从侧面来说,提高了效率,节省了空间。
  3. 引用比起指针来,更容易使用,语法,语意上也更加清晰。

常引用的目的:既想提高效率,又不愿意参数在函数体中被修改,所以采用常引用。

引用与多态的关系:

曾经测试过这样的关系,写过代码,用来实现表达的是:一个基类的引用可以指向其派生类的实例。

4:多态的作用

1) 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用。

2) 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性的正确使用。

5:什么情况下只能使用成员初始化列表,而不能用赋值?

1) 类中有const,reference成员变量,对他们进行初始化的时候,只能是在成员初始化列表中。为什么呢?const,reference在定义的时候,都必须初始化,以后都不可更改,正因为有这样的属性,若是在类的构造函数中对他们进行初始化,则就有被修改的可能。所以,对他们的初始化放在初始化列表中,在未进入构造函数的时候,就完成初始化工作。

2) 派生类构造函数调用基类的构造函数的时候都要使用初始化列表。

理解:派生类定义对象的时候,首先要调用基类的构造函数进行初始化,再是派生类自己的构造函数进行初始化工作。而在派生类的构造函数中,使用初始化列表的形式,去调用基类的构造函数,完成初始化操作。

面试宝典上一道题的分析:(多态)

C++回顾复习(续1)C++回顾复习(续1)代码

#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:重载,覆盖,隐藏的比较分析

重载:

  1. 同一个类中谈论重载才具有意义
  2. 函数名字相同,但参数个数不同,或者类型不同
  3. virtual,可有可无

覆盖:(子类覆盖基类)

  1. 不同类中,才谈论覆盖;在父类和子类中才存在这种关系
  2. 函数名字相同,参数相同
  3. 最重要的一点:必须是virtual函数,只有虚函数存在的前提下,才谈论覆盖的问题。

隐藏:

这个比较生疏,接触少些。

  1. 也是在不同类中,基类和派生类中
  2. 派生类和基类有同名函数,但参数不同,不管有无virtual,基类的这个函数将会被隐藏。
  3. 派生类和基类有同名函数,且参数相同,但不是virtual,基类函数被屏蔽。


原文链接: https://www.cnblogs.com/marrywindy/archive/2010/08/18/1802135.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/13980

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午1:27
下一篇 2023年2月7日 下午1:28

相关推荐