Essential C++学习笔记------第五章

来源:互联网 发布:网络电话机终端 编辑:程序博客网 时间:2024/04/30 19:39

面向对象编程风格

      第四章中的基于对象编程风格,主要特性是封装,所谓封装是将事物的属性和在于它之上的操作封装起来成为一个类或对象,但对于类间存在的关系则需要面向对象编程模型,面向对象的主要特征是继承和多态,继承可以让我们定义一群互有关系的类(如基类和派生类),并且共享公共接口。而多态得以让我们以一种以型别无关的方式来操作这些类对象,我们通过抽象基类的指针或引用来操控这些公共接口,而实际执行起来的操作则需要等到执行期,依据指针或引用所实际寻址的对象的型别才能决定。可见动态绑定和多态特性必须要在使用指针或引用时才能发挥作用。当以一个对象的名义来调用函数,就是静态绑定。当以指针或引用的方式才是动态绑定。我们也可以在调用函数之时,用上域作用符::清楚明白指出调用哪个对象的函数,实现静态绑定。

     1.静态绑定和动态绑定

      假设有一个抽象基类为LibMat,其下有Book,Magazines,Files等派生类,在LibMat 内定义了一组共通的操作,比如check_in()函数。

      定义一个对象,LibMat &mat;

     在非面向对象的编程模型中,当我们写下这条语句 mat.check_in();编译器在编译期就能根据mat的型别来决定哪个check_in()函数,这种在执行之前就能知道要调用哪个函数的方式就叫做静态绑定。

     而在面向对象编程中,只有在执行期中依据mat的实际寻址对象才能决定调用哪个派生类的函数,这种方式就叫动态绑定。

    那么,怎么才能告知编译器要实现动态绑定方式呢?

   答案就是虚函数,我们只需要在函数声明前加上virtual关键词即可。每个虚函数要么有定义,要么可设为纯虚函数。就是说基类中虚函数不能仅仅有声明,它要么是声明的

同时定义,要么是令其值为0表纯虚函数,如:virtual const char * what_am_i() const =0;可设为虚函数的条件是在基类中无实际意义。如果析构函数(假如这是个虚函数)里并无实际要操作的对象,一般不建议设为纯虚函数,而是加空函数体。

  

     2.派生类的定义

    唯一规则是定义派生类时,基类定义要先存在,可先引入包含基类的头文件,或基类也定义在同个文件的上方。

     class Book : public LibMat{//类名后紧跟冒号,然后加关键词public和基类名称,表示类Book派生自 LibMat类,当然继承还有private,protected继承等。

         public:

            ..........................//

         private:

            .........................//

     };

    2.1 如何在派生类中实现对虚函数的重写

    在派生类定义内部中实现重写时和基类中一样,在前面加上virtual,其余和一般成员函数一样。

   在派生类定义的外部重写时,无须加virtual,和一般函数一样即可。

   静态成员函数无法被声明为虚函数。

   我们是无法为抽象类定义对象的,如抽象基类,如果派生类完全继承了纯虚函数而没有重写,那这个派生类也被视为抽象类,也无法为之定义对象。

    2.2 在调用派生类的构造函数之前会先调用基类的构造函数,在调用派生类的析构函数之后会默认调用基类的析构函数。

根据一般规则,凡是基类中存在虚函数的话,其析构函数要设为虚函数,而构造函数并无此要求。要不要定义构造函数主要是看类中有无非静态数据成员需要初始化。

如有,则有必要定义。

   如果析构函数(假如这是个虚函数)里并无实际要操作的对象,一般不建议设为纯虚函数,而是加空函数体。

   2.3 当在基类和派生类中提供同名的成员时,虚函数除外,通过派生类调用的会被认为是派生类的同名成员,要调用基类中同名成员必须用域作用符。

   2.4引用和指针的选择。

   我们知道引用永远无法代表空对象,指针却有可能是null,所以定义为引用时不用检查是否为空,这是一个优势。

   但是若某个成员是引用类型,则需要在构造函数中利用成员初始化表进行初始化,并且一旦绑定不能再改变,但如果是指针类型,则可以在构造函数内初始化,也可以

先暂时设为空,稍后再指向有效内存。

   

    2.5虚函数的静态决议

    虚函数动态绑定失效的两种情况是,(1)在基类的构造函数和析构函数内调用虚函数时,调用的将会是基类那份,因为在基类构造函数调用时,派出类的成员尚示初始化,

如果调用派生类中的虚函数则会出错。在析构函数中,由于派生类先被析构,所以同样道理,调用的仍会是基类的那份。

     (2)使用了基类对象,而非基类引用或指针。

   2.6执行期的型别鉴定机制

   所谓执行期的型别鉴定是,我们可以在程序执行过程中,利用相关手段,以获得多态化的引用或指针所指向对象的实际型别。在C++中,这种手段就是利用typeid运算符。

用这个运算符之前需包含头文件typeinfo,如#include<typeinfo>。typeid运算符会返回一个type_info对象,这个对象存储着与型别相关的种种信息,如该对象的name()函数

返回一个const char *表实际类型名称。如下面的函数

 #include<typeinfo>

  what_am_i()

 {

      return typeid(*this).name();

 }

   typeid参数也可以类名称

  如:if(typeid(*this)==typeid(Fabonacci))如以判断是否是Fabonacci类型。

  2.7类型转换

  2.7.1 static_cast 无条件转换,假如类型不一致,存在风险,所以必须先用typeid看看类型是否一致

   如:if(typeid(*this)==typeid(Fabonacci))

           {

                  Fabonacci *pf=static_cast<Fabonacci *>(this);

            }

  2.7.2 dynamic_cast 有条件转换,会先执行类型检查,如果一致则转,否则返回空指针。

       Fabonacci *pf=dynamic_cast<Fabonacci *>(this);