敬业的IT人 >> 编程开发 >> C/C++ >> C++中类的继承特性

C++中类的继承特性

敬业的IT人 互联网 佚名 2008-1-3 19:39:56
引用:

  整个c++程序设计全面围绕面向对象的方式进行,类的继承特性是c++的一个非常非常重要的机制,继承特性可以使一个新类获得其父类的操作和数据结构,程序员只需在新类中增加原有类中没有的成分。

  可以说这一章节的内容是c++面向对象程序设计的关键。

  下面我们简单的来说一下继承的概念,先看下图:

  C++中类的继承特性(图一)

  • 如何重装xp系统图解
  • 下载Flash播放插件
  • 巧妙清除Windows 2000/XP登录密码
  • 如何利用路由器设置局域网
  • QQ空间打不开
  • 开机后鼠标不动怎么办
  • Excel密码保护的解除方法与解除原理
  • Windows XP注册表详解
  • 3dmax不锈钢金属材质的制作方法
  • 硬盘变成raw格式怎么办

  上图是一个抽象描述的特性继承表

  交通工具是一个基类(也称做父类),通常情况下所有交通工具所共同具备的特性是速度与额定载人的数量,但按照生活常规,我们来继续给交通工具来细分类的时候,我们会分别想到有汽车类和飞机类等等,汽车类和飞类同样具备速度和额定载人数量这样的特性,而这些特性是所有交通工具所共有的,那么当建立汽车类和飞机类的时候我们无需再定义基类已经有的数据成员,而只需要描述汽车类和飞机类所特有的特性即可,飞机类和汽车类的特性是由在交通工具类原有特性基础上增加而来的,那么飞机类和汽车类就是交通工具类的派生类(也称做子类)。以此类推,层层递增,这种子类获得父类特性的概念就是继承。

  下面我们根据上图的理解,有如下的代码:

#include<iostream>  
usingnamespacestd; 
 
classVehicle 
{ 
public: 
   voidEditSC(floatspeed,inttotal); 
protected: 
   floatspeed;//速度 
   inttotal;//最大载人量 
}; 
voidVehicle::EditSC(floatspeed,inttotal) 
{ 
   Vehicle::speed=speed; 
   Vehicle::total=total; 
} 
classCar:publicVehicle//Car类继承Vehicle的特性,Car类是Vehicle的派生类 
{ 
public: 
   Car() 
   { 
     aird=0; 
   } 
protected: 
   intaird;//排量 
}; 
 
classplane:publicVehicle 
{ 
protected: 
   floatwingspan;//翼展 
}; 
 
voidmain() 
{ 
   Cara; 
   a.EditSC(150,4); 
   cin.get(); 
}

  派生类的定义可以在类名称后加冒号public空格加基类名称进行定义,如上面代码中的class Car:public Vehicle。

  • 如何重装xp系统图解
  • 下载Flash播放插件
  • 巧妙清除Windows 2000/XP登录密码
  • 如何利用路由器设置局域网
  • QQ空间打不开
  • 开机后鼠标不动怎么办
  • Excel密码保护的解除方法与解除原理
  • Windows XP注册表详解
  • 3dmax不锈钢金属材质的制作方法
  • 硬盘变成raw格式怎么办

  至于为什么派生类能够对基类成员进行操作,我们上图可以简单的说明基类与子类在内存中的排列状态。

  我们知道,类对象操作的时候在内部构造的时候会有一个隐的this指针,由于Car类是Vehicle的派生类,那么当Car对象创建的时候,这个this指针就会覆盖到Vehicle类的范围,所以派生类能够对基类成员进行操作。

  笔者写到这里的时候不得不提一下,我有开发c#与java的经验,就这两种语言来说,学到这里的时候很多人很难理解继承这一部分的内容,或者是理解的模糊不清,其实正是缺少了与this指针相关的c++知识,多数高级语言的特性是不涉及内存状态的操作,java与c#是接触不到这些知识的,所以理解起这部分内容就更抽象更不具体。

  下面我们来说一下,派生类对象(子类对象)的构造。

  由上面的例程我们知道Car类是Vehicle类的派生类(子类),c++规定,创建派生类对象的时候首先调用基类的构造函数初始化基类成员,随后才调用派生类构造函数。

  但是要注意的是,在创建派生类对象之前,系统首先确定派生类对象的覆盖范围(也可以称做大小尺寸),上面代码的派生类对象a就覆盖于Vehicle类和Car类上,至于派生类对象的创建是如何构造基类成员的,我们看如下代码,随后再进行分析:

#include<iostream>  
usingnamespacestd; 
 
classVehicle 
{ 
public: 
   Vehicle(floatspeed=0,inttotal=0) 
   { 
     cout<<"载入Vehicle类构造函数"<<endl; 
     Vehicle::speed=speed; 
     Vehicle::total=total; 
   } 
   Vehicle(Vehicle&temp) 
   { 
     Vehicle::speed=temp.speed; 
     Vehicle::total=temp.total; 
   } 
   ~Vehicle() 
   { 
     cout<<"载入Vehicle类析构函数"<<endl; 
     cin.get(); 
   } 
protected: 
   floatspeed;//速度 
   inttotal;//最大载人量 
}; 
classCar:publicVehicle 
{ 
public: 
   Car(floataird=0,floatspeed=0,inttotal=0):Vehicle(speed,total) 
   { 
     cout<<"载入Car类构造函数"<<endl; 
     Car::aird=aird; 
   } 
   Car(Car&temp):Vehicle(temp.speed,temp.total) 
   { 
     cout<<"载入Car类拷贝构造函数"<<endl; 
     Car::aird=temp.aird; 
   } 
   ~Car() 
   { 
     cout<<"载入Car类析构函数"<<endl; 
     cin.get(); 
   } 
protected: 
   floataird;//排量 
}; 
voidmain() 
{ 
   Cara(250,150,4); 
   Carb=a; 
   cin.get(); 
}

  对象a创建的时候通过Car(float aird = 0,float speed = 0,int total = 0):Vehicle(speed,total),也就是Car类构造函数,来构造Car类对象成员,但按照c++的规定首先应该调用基类构造函数构造基成员,在这里基类成员的构造是通过Vehicle(speed,total),来实现的。

进入讨论组讨论。

  但值得注意的是Vehicle(speed,total)的意义并不是对Vehicle类的个个成员的初始化,事实上是利用它创建了一个Vehicle类的无名对象,由于Car类对象的覆盖范围是覆盖于Vehicle类和Car类上,所以系统在确定了派生类对象a的空间范围后,确定了this指针位置,这个this指针指向了Vehicle类的那个无名对象,这个成员赋值过程就是,this->speed=无名对象.speed。

  其实这里概念比较模糊,笔者因为个人能力的原因暂时也无法说的更明确了,读者对此处知识点的学习,应该靠自己多对比多练习,进行体会理解。

  许多书籍对于派生类对象的复制这一知识点多是空缺的,为了教程的易读性,我还是决定说一下在复制过程中容易出错的地方,Car b=a;是派生类对象复制的语句,通过前面教程的学习我们我们知道,类对象的复制是通过拷贝构造函数来完成的,如果上面的例子我们没有提供拷贝构造函数不完整如下:

Car(Car&temp) 
{ 
   cout<<"载入Car类拷贝构造函数"<<endl; 
   Car::aird=temp.aird; 
}

  那么复制过程中就会丢失基类成员的数据了,所以Car类拷贝构造函数不能缺少Vehicle类无名对象的创建过程:Vehicle(temp.speed,temp.total),派生类对象的复制过程系统是不会再调用基类的拷贝构造函数的,this指针的问题再次在这里体现出来,大家可以尝试着把无名对象的创建去掉,观察b.speed的变化。

  类对象够创建必然就有析构过程,派生类对象的析构过程首先是调用派生类的析构过程,再调用基类的构造函数,正好和创建过程相反,在这里笔者已经在上面代码中加入了过程显示,读者可以自行编译后观察。

  最后我们说一下类的继承与组合。

  其实类的组合我们在早些的前面的教程就已经接触过,只是在这里换了个说法而已,当一个类的成员是另一个类的对象的时候就叫做组合,事实上就是类于类的组合。组合和继承是有明显分别的,为了充分理解继承与组合的关系,我们在不破坏类的封装特性的情况下,先看如下的代码:

#include<iostream> 
usingnamespacestd; 
 
classVehicle 
{ 
public: 
   Vehicle(floatspeed=0,inttotal=0) 
   { 
     Vehicle::speed=speed; 
     Vehicle::total=total; 
   } 
protected: 
   floatspeed;//速度 
   inttotal;//最大载人量 
}; 
 
classMotor 
{ 
public: 
   Motor(char*motor) 
   { 
     Motor::motortype=motor; 
   } 
 
   char*SMT(Motor&temp); 
protected: 
   char*motortype;//发动机型号 
}; 
char*Motor::SMT(Motor&temp) 
{ 
   returntemp.motortype; 
} 
 
classCar:publicVehicle//继承的体现 
{ 
public: 
   Car(floatspeed,inttotal,intaird,char*motortype):Vehicle(speed,total),motor(motortype) 
   { 
     Car::aird=aird; 
   } 
 
   Motorrm(Car&temp); 
protected: 
   intaird;//排量 
   Motormotor;//类组合的体现 
}; 
 
MotorCar::rm(Car&temp) 
{ 
   returntemp.motor; 
} 
 
//-------------------------------------------------------------- 
voidtest1(Vehicle&temp) 
{ 
   //中间过程省略 
}; 
voidtest2(Motor&temp) 
{ 
   cout<<temp.SMT(temp);//读者这里注意一下,temp既是对象也是对象方法的形参 
} 
//-------------------------------------------------------------- 
 
voidmain() 
{ 
   Cara(150,4,250,"奥地利AVLV8"); 
   test1(a); 
   //test2(a);//错误,Car类与Motor类无任何继承关系 
   test2(a.rm(a));//如果Car类成员是public的那么可以使用test2(a.motor) 
   cin.get(); 
}

  在上面的代码中我们新增加了发动机类Motor,Car类增加了Motor类型的motor成员,这就是组合,拥有继承特性的派生类可以操作基类的任何成员,但对于与派生类组合起来的普通类对象来说,它是不会和基类有任何联系的。

本文地址:进入讨论组讨论。

  函数调用:test1(a);,可以成功执行的原因就是因为Car类对象在系统是看来一个Vehicle类对象,即Car类是Vehicle类的一种,Car类的覆盖范围包含了Vehicle。

  函数调用:test2(a);,执行错误的原因是因为Motor类并不认可Car类对象a与它有任何关系,但我们可以通过使用Car类对象a的Motor类成员motor,作为函数形参的方式来调用test2函数(test2(a.motor)),在这里由于类的成员是受保护的所以我们利用a.rm(a)来返回受保护的motor,作为函数参数进行调用。

本文地址:进入讨论组讨论。
粤ICP备06119539号
Copyright CiscoSky.Org,Some Rights Reserved.
Email:me1228#tom.com