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)      派生类构造函数调用基类的构造函数的时候都要使用初始化列表。

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

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

 

代码
#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,基类函数被屏蔽。

 

posted on 2010-08-18 10:55  marrywindy  阅读(290)  评论(0编辑  收藏  举报

导航