加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

[转载]C++学习第一部分:继承(三)

(2012-06-14 16:07:35)
标签:

转载

分类: 计算机相关

类型兼容规则

l  一个公有派生类的对象可以替代基类的对象(反之则禁止):

  派生类的对象可以被赋值给基类对象。(eg:学生至少是一个人)

  派生类的对象可以初始化基类的引用。

  指向基类对象的指针也可以指向派生类对象。

l  通过基类对象名、引用名、指针只能使用从基类继承来的成员。窄化效应切割

l  此规则又称“类型包容法则”、“向上兼容性”、“向上映射”。

l  类型兼容的例子:

class Base  {

    int m_a , m_b ;         

};

class Derived : public Base   {

    int m_c ;  

};

void main( void )     

{           Derived objD1;

      Base objB1= objD1;          //发生类型转换(切割)

      Derived *pD1 = &objD1;

      Base *pB1 = pD1;                 //发生类型转换(切割)

      Base & robjB1 =objD1;

}

[转载]C++学习第一部分:继承(三)

这还意味着:1子类对象赋值给父类对象时,父类对象的表面类型真实类型是一致的。 会出现真切割,2 3则会出现指针(或引用)的“表面类型”和它所指向的对象的“真实类型”的不一致现象,是假切割。这种貌合神离恰是动态多态的魅力所在,也给人带来了困惑和理解的难度。

于是就有“静态类型”“动态类型”之说。

多层继承后,尽管用指针指向各类对象,但“切割”作用使子类的同名函数仍不能被调用。

#include <iostream.h>

#include “conio.h“ //用于提供getch() 函数

class k                         //k基类声明

{  public:

         void at(int a){cout <<“进入K 类!”<<a<<endl; }

};

class kk: public k                //kk基类声明

{   public:

         void at(char a){ cout <<"进入KK 类!"<<endl;          cout<<a<<"-------------"<<endl;}

};

class xq: public kk      

{

public:

         void at(float f)       //(局部)对象成员

         {cout <<“进入XQ 类!(按任意键继续)<<f<<endl<<endl; }

};

void main()                       //主函数

{

         cout <<"进入主函数!!!!!!!!!!!!"<<endl;

         k * p;                

         p=new k;      //父对象

         p->at(10);  // 访问的是k中的at(int)

         p=new kk;    //子对象

         p->at(‘i’); // 访问的还是at(int)

         p=new xq;    //孙对象

         p->at(10.88); // 访问的还是at(int)

         getch();    //‘接受一键’函数——起到停屏作用

}

运行结果:

进入主函数!!!!!!!!!!!!

进入K 类!10

进入K 类!105

进入K 类!10

 

注:C++中所有new出来的对象都要赋给指针,

P->at(10)这个操作就的实质是(*p).at(10),指针指向了堆中的对象,无名但是有地址。

因为p是基类类型的指针,表面类型是子类,因此造成了假切割,即调用的方法是继承下来的基类部分的方法。105iASCII码值,第三个10是把10.88截断了,造成了失真。

类型兼容千万不要用在数组上

子类对象确实是父类对象的一种。但子类对象的数组却不是父类对象数组的一种。

 class  B  {                      class  D :public B  {

       //…                                         //…

  };                                     };

D  Array[5];//这一步发生的事情是:先调用父类B的构造函数,再调用D的构造函数,连//续这样5

B  * p = Array; 

此时可以用Array[i] 访问每个元素,可是能用 p[i]访问吗?Why?

 画出内存存储图示就明白了!

 [转载]C++学习第一部分:继承(三)

 

如果用数组来访问,则Array这个名称相当于是指针,可以挨个访问D的对象。但是如果用p来访问,因为pB类型的,它默认的一个“步长”是B类型对象的一个大小,也就是p的步长仅仅是D类对象中的B类部分(橘黄色区域),如果直接用p++,跨出的一步会仍包含前一个D类对象中自己新增的部分(蓝色区域),这样访问下去会造成混乱,如果一旦再发生值的修改,就可能造成内存垃圾甚至系统崩溃。

类型兼容也不要用在二级指针上

用父类的指针指向子类对象确实是一种常用的手法:

    Circle  * cp = new Circle ;

    Shape  * sp = cp ;

但指向父类指针的指针却不是指向子类指针的指针的一种:

    Circle  ** ccp = &cp;

    Shape  ** ssp = ccp ;      Why?    画出存储图示就明白了!

[转载]C++学习第一部分:继承(三)

 

 

circle类型指针的指针 = shape类型指针的指针

ccp指向cpcp指向circle对象,则ccp是指向circle类型的指针的指针;

ssp是指向shape类型指针的指针。

多继承时的对象指针

class B1   {       

     public:    ...

     private:

                int b1;

};

class B2   {       

     public:    ...

     private:

                int b2;

};

 

class D :public B1 public B2  {

    public:     ...

    private:

                int d;

};

D  *pd = new d;

B1 * pb1 = pd;//预定义转换

B2 * pb2 = pd;//预定义转换

[转载]C++学习第一部分:继承(三)

 

上图给出了子类对象两种可能的布局。尽管直观看起来指针指的位置不同,但是用户在使用上并无差别,即:

  if ( pd == pb1 ) 

  if ( pb2 == pd ) 

比较结果都是 true.三个指针都指向同一个对象。(不能进行  pb2 == pb1 比较!因为一个是B1类型,一个是B2类型 )

如果用输出语句显示这三个指针的地址,都是一样的,实际上其内幕是: 多继承时编译器记下了各个父类在子类对象中的偏移量,上一页所谓 预定义的转换就是加或减去偏移量.

由此我们可以得出结论——也是重要经验:

在使用指向对象的指针或引用时,千万不要丢失了类型,类型名在这里起着关键的作用。一旦将其交给了void *,则类型信息尽失,出错就是必然的。

0

  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有