C++类的继承剖析

来源:互联网 发布:ae cc2017 mac 中文包 编辑:程序博客网 时间:2024/05/08 19:38
                 类的继承机制
一. 类之间的关系
   has—A,uses—A和is—A
has—A:包含关系,用于描述一个类由多个“部件类”构成(包括成员类对象和复合类两种)。实现has—A关系.用类成员表示,即一个类中的数据成员是另一种已经定义好的类。
use—A:一个类部分的使用另一个类。通过类之间成员函数的相互联系,定义友元或对象参数传递实现。
is—A:机制称为继承。关系具有传递性,不具有对称性。
注意:包含关系是表示一个对象有其他类作为部件,是表示一个类表示的对象是另一个类表示对象的一部分,往往没有密切的联系。比如:鼻子,眼睛,耳朵都是头的一部分。那么在定义“头”这个类时,鼻子,眼睛,耳朵类都是头的成员类,必须在“头”类中定义这些成员类的对象。还有比如油门,刹车,车轮都是汽车的一部分,那么在定义“汽车”类时,油门,刹车,车轮都是汽车类的成员类,就需要在汽车类中定义这些零件类的对象。而继承关系是表示种属关系,就是说一个类表示的对象属于另一个类表示的对象的一种类型,有很密切的联系往往是一个对象在另一对象的基础之上(具有就是继承了另一对象的所有特性),又增添了自己特有的功能特性。例如,man属于human的一种,具有human的所有特征,又增添了自己的特征例如长胡须,......等等。



(1) 继承是类之间定义的一种重要关系。
(2)一个 B 类继承A类,或称从类 A 派生类 B,类 A 称为基类(父类),类 B 称为派生类(子类)

二.基类和派生类

 类继承关系的语法格式
      class 派生类名:基类名表
  {
          数据成员和成员函数说明
   }

 基类名表  构成:
             访问控制 基类名1,访问控制  基类名2,......,访问控制  基类名n
 访问控制  表示派生类对基类的继承方式,使用关键字:
public   共有继承
private   私有继承
protected  保护继承

访问控制:
派生类对基类成员的使用,与继承访问机制和基类中的成员性质有关
公有继承:基类中的公有成员——>被继承为派生类的公有成员
                  基类中的保护成员——>被继承为派生类的保护成员
私有继承:基类中的公有成员和保护成员——>被继承为派生类的私有成员
保护继承:基类的公有成员和保护成员——>被继承为派生类的保护成员
不论哪种方式继承基类,派生类都不能直接使用基类的私有成员

1.公有继承


 公有继承测试,代码如下:
#include <iostream>using namespace std;class A{    public :    void  get_XY(){        cout<<"Enter two numbers of x,y: ";        cin>>x>>y;    }    void put_XY(){        cout<<"x= "<<x<<", y="<<y<<endl;    }    protected :    int x,y;};class B:public A{    public :    int get_S(){return s;}    void make_S(){        s=x*y;/*说明在公有继承中,基类中的保护数据成员在类层次中可见*/    }/*使用基类成员x,y*/    protected :int s;};class C:public B{    public :    void get_H(){        cout<<"Enter a number of h : ";        cin>>h;    }    int get_V(){ return  v;}    void make_V()    {        make_S();        v=(get_S())*h;    }    protected :    int h,v;};int main(){    A objA; /*成员有objA.x ,objA.y*/    B objB; /*成员有objB.x,objB.y,还有自己拓展的成员objB.s*/    C objC;/*成员有objC.x,objC.y,objC.s,objC.h,objC.v*/    cout<<"It is object_A:"<<endl;    objA.get_XY(); /*对objA的数据成员操作*/    objA.put_XY();    cout<<"It is objecy_B:"<<endl;    objB.get_XY(); /*调用继承基类A的成员函数,对objB的数据成员进行操作*/    objB.make_S();/*调用派生类B的成员函数,对objB的数据成员进行操作*/    cout<<"S= "<<objB.get_S()<<endl;    cout<<"It is object c: "<<endl;    objC.get_XY();/*调用从基类A通过类层次继承下来的的成员函数,对objC的数据成员进行操作*/    objC.get_H();/*调用派生类C成员函数对 objC 的数据成员操作*/    objC.make_V();    cout<<"V=  "<<objC.get_V()<<endl;    return 0;}

运行结果如下:


2.私有继承:


私有继承测试1,代码如下:
#include <iostream>using namespace std;class A{    public :    void  get_XY(){        cout<<"Enter two numbers of x,y: ";        cin>>x>>y;    }    void put_XY(){        cout<<"x= "<<x<<", y="<<y<<endl;    }    protected :    int x,y;};class B:private A{    public :    int get_S(){return s;}    void make_S(){        get_XY();/*调用从基类继承来的成员函数*/        s=x*y;/*访问从基类继承来的私有数据成员*/    }/*使用基类成员x,y*/    private :    int s;};int main(int argc, const char * argv[]){    B objB;    cout<<"It is object B"<<endl;    objB.make_S();    cout<<"S=  "<<objB.get_S()<<endl;    return 0;}

运行结果如下:



私有继承测试2,代码如下:
#include <iostream>using namespace std;class A{    public :    A(){ x=1;}    int out(){return x;}/*基类中的私有成员不能在派生类中直接访问,但存储空间及内容仍在                         可以通过公有函数返回私有成员的值以便于在派生类或外部进行访问*/    void addX(){ x++;}    private :  int x;};class B:public A{    public : B(){ y=1;}    int out(){  return y;}    void addY(){ y++;}    private :    int y;};int main(int argc, const char * argv[]){    A a;    B b;/*创建派生类对象,调用基类的默认构造函数x=1再调用派生类的构造函数 y=1*/    b.addX(); b.addY();    cout<<"a.x= "<<a.out()<<endl;    cout<<"b.x= "<<b.A::out()<<endl;/*调用基类版本的同名函数,返回b.x*/    cout<<"b.y= "<<b.out()<<endl;/*调用派生类版本的同名函数,返回b.y*/    /*注意:当派生类的成员与基类的不同名时,基类的非私有成员都可以看成派生类的成员。当重名时     直接调用成员,调用的是派生类实现的成员。若想调用基类版本的同名成员,需要用     “ 派生类对象名.基类名::同名成员名 ” 进行实现*/    return 0;}
运行结果如下:


3.保护继承



4.重命名成员
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员。
在派生类中使用基类的同名成员,需要显式的使用类名限定符: 类名::成员,就相当于有两个版本的同名成员,一个是基类版本的成员,另一个是派生类拓展的成员。如果不加限定符,系统会默认调用派生类版本的同名成员。
(基类成员的作用域延伸到所有的派生类,派生类的重命名成员屏蔽基类)

重命名测试代码如下所示:
#include <iostream>using namespace std;class A{    public :    int a1,a2;    A(int i1=0,int i2=0){ a1=i1; a2=i2;}    void print()    {        cout<<"a1= "<<a1<<'\t'<<"a2= "<<a2<<endl;    }};class B:public A{    public :    int b1,b2;    B(int j1=1,int j2=1){ b1=j1; b2=j2;}    void print(){/*定义同名函数*/        cout<<"b1= "<<b1<<'\t'<<"b2= "<<b2<<endl;    }    void printAB()    {   A::print();/*派生类对象调用基类版本的同名成员函数*/        print();/*派生类调用自身的成员函数,屏蔽了基类同名函数*/    }};int main(int argc, const char * argv[]){    B b;    b.A::print();/*派生类对象调用从基类继承的同名成员函数,基类的this指针指向派生类对象*/    b.printAB();    return 0;}/*注意:通过继承,类B具有两个同名成员函数 void A::print() //void print(A *this) void B::print()//void print(B *this) 派生类也是基类,基类指针可以指向派生类对象 派生类中定义与基类同名的成员函数,称为重载成员函数 */



5 基类的初始化问题
(1)建立一个类层次后,通常创建某个派生类的对象,包括隐含使用基类的数据和函数。
(2)构造函数不被继承。C++提供一种机制,在创建类对象时调用基类的构造函数来初始化基类数据。
(3)派生类构造函数声明为:
   派生类构造函数(变元表):基类(变元表),对象成员1(变元表),.........,对象成员n(变元表)
(4)构造函数执行顺序:基类——>对象成员——>派生类
(5)基类构造函数只能在派生类构造函数的初始化列表(也称初始化构造器)进行赋值初始化,在派生类构造函数的函数体内进行初始化时不行的。
注意:

 1 实现派生类的构造函数时,基类的构造函数只能在派生类构造函数的初始化列表中进行初始化,如果在派生类构造函数体内就无法实现。

 如果在派生类构造函数的初始化列表中没有调用基类的构造函数对基类成员进行初始化,系统将调用父类的默认构造函数(无参或有默认值其中唯一一个默认构造函数)对基类成员进行初始化赋值,如果基类没有默认构造函数,那么在派生类实现构造函数时就必须在初始化列表中调用父类构造函数进行初始化。在实现派生类的构造函数时,如果其基类没有默认的构造函数且派生类的初始化列表中又没有调用基类的构造函数,将会造成编译错误。

3 类的默认构造函数分为三种情况:一不提供任何构造函数,系统将提供一个默认的无参构造函数,不会改变成员值。二手动提供一个无参构造函数,对成员的初始化在函数体内进行实现,系统将它视为默认构造函数。 三 手动提供一个有默认值的构造函数,系统也会将它视为默认构造函数。

 切记:一旦为类提供任何的构造函数,系统将不会为它提供一个默认的构造函数;只能为类提供一个(仅仅一个)默认构造函数,如果提供了无参的默认构造函数的同时又提供了有默认值的默认构造函数,将会编译错误。


 调用构造函数顺序测试,构造函数无参数,代码如下:
#include <iostream>using namespace std;class Base{    public :    Base(){        cout<<"Base class created"<<endl;    }};class D:public Base{    public :    D(){        cout<<"D class created"<<endl;    }};int main(int argc, const char * argv[]){    D d1;    return 0;}
运行结果如下:


带参数构造函数调用顺序测试代码如下:
#include <iostream>using namespace std;class parent_class{    int private1,private2;    public :    parent_class(int p1,int p2)    {        private1=p1;        private2=p2;    }    int inc1(){ return  ++private1;}    int inc2(){ return ++private2;}    void display(){        cout<<"private1= "<<private1<<", private2= "<<private2<<endl;    }};class derived_class:private parent_class{    int private3;    parent_class private4;//类成员    public :    derived_class(int p1,int p2,int p3,int p4,int p5):parent_class(p1,p2),private4(p3,p4)    /*先调用基类构造函数,再构造成员对象*/    { private3=p5;}/*对自身数据成员赋值*/    int inc1(){ return parent_class::inc1();}/*调用基类版本的成员函数*/    int inc3(){ return ++private3;}    void display()    {        parent_class::display();/*调用基类成员函数,输出从基类继承的数据成员*/        private4.display();/*调用对象成员的函数,输出对象成员数据*/        cout<<"private3= "<<private3<<endl;    }};int main(int argc, const char * argv[]){    derived_class d1(17, 18, 1, 2, -5);    d1.inc1();    d1.display();    return 0;}

运行结果如下:


继承的应用实例(考察一个点、圆、圆柱体的层次结构):代码如下
#include <iostream>#include <iomanip>using namespace std;class Point{    friend ostream& operator<<(ostream &,const Point &);    public :    Point(int=0,int=0);  /*带默认参数的构造函数*/    void setPoint(int,int);    int getX() const {return x;}    int getY() const{ return y;}    protected : int x,y; /*Point 类的数据成员*/};class Circle:public Point{    friend ostream& operator<<(ostream &,const Circle &);/*友元函数*/    public :    Circle(double r=0.0,int x=0,int y=0); /*构造函数*/    void setRadius(double); /*置半径*/    double getRadius() const; /*返回半径*/    double area() const;  /*返回面积*/    protected :    double radius;/*数据成员,半径*/};class Cylinder:public Circle{    friend ostream& operator<<(ostream &,const Cylinder &); /*友元函数*/    public :    Cylinder(double h=0.0,double r=0.0,int x=0,int y=0); /*构造函数*/    void setHeight(double); /*置高度值*/    double getHeight() const; /*返回高度值*/    double area() const; /*返回面积*/    double volume() const; /*返回体积*/    protected : double height; /*数据成员,高度*/};//Point类的成员函数//构造函数,调用成员函数对x,y进行初始化Point::Point(int a,int b){    setPoint(a, b);  }/*对数据成员赋值*/void Point::setPoint(int a, int b){   x=a; y=b;}//重载插入运算符,输出对象数据(注意:友元函数是全局函数,并不是类的成员函数,所以不可以在实现时用“ 类名::友元函数” 进行实现)ostream& operator<<(ostream &output,const Point &p){    output<<'['<<p.x<<", "<<p.y<<']';    return output;}/*Circle类的成员函数*///带初始化式的构造函数,首先调用基类构造函数Circle::Circle(double r,int a,int b):Point(a,b){ setRadius(r);}//对半径赋值void Circle::setRadius(double r){ radius=(r>=0? r:0);}//返回半径值double Circle::getRadius() const{ return radius;}//计算并返回面积值double Circle::area() const{ return 3.14159*radius*radius;}//输出圆心坐标与半径值ostream& operator<<(ostream &output,const Circle &c){    output<<"Center= "<<'['<<c.x<<", "<<c.y<<"]"<<"; Radius= "    <<setiosflags(ios::fixed|ios::showpoint)<<setprecision(2)<<c.radius<<endl;    return output;}/*Cylinder类的成员函数*///带初始化构造函数,首先调用基类构造函数Cylinder::Cylinder(double h,double r,int x,int y):Circle(r,x,y){ setHeight(h);}//给高度赋值void Cylinder::setHeight(double h){ height=(h>=0?h:0);}//返回高度值double Cylinder::getHeight() const{ return  height;}//计算并返回圆柱体的表面积double Cylinder::area() const{ return 2*Circle::area()+2*3.14159*radius*height;}//计算并返回圆柱体的体积double Cylinder::volume() const{ return Circle::area()*height;}//输出数据成员圆心坐标、半径和高度值ostream &operator<<(ostream &output,const Cylinder& cy){    output<<"center= "<<"["<<cy.x<<", "<<cy.y<<"]"<<", Radius= "    <<setiosflags(ios::fixed|ios::showpoint)<<setprecision(2)<<cy.radius    <<"; Height= "<<cy.height<<endl;    return  output;}int main(int argc, const char * argv[]){    Point p(72,115); //定义点对象并初始化    cout<<"The inital location of p is "<<p<<endl;    p.setPoint(10, 10); //给点设置新的值    cout<<"\nThe new location of p is "<<p<<endl;    Circle c(2.5,37,43); //定义圆对象并初始化    cout<<"\nThe inital location and radius of c are\n"<<c<<"\nArea= "<<c.area()<<endl;    c.setRadius(4.25); c.setPoint(2, 2);//给圆设置新的数据值    cout<<"The new location and radius of c are \n"<<c<<"\nArea= "<<c.area()<<endl;    Cylinder cy1(5.7,2.5,12,23); //定义圆柱体对象并初始化    //输出圆柱体的各数据和表面积,体积    cout<<"\nThe inital location,radius and height of cy1 are\n"<<cy1    <<"Area= "<<cy1.area()<<"\nVolume= "<<cy1.volume()<<endl;    //给圆柱体设置新的数据值    cy1.setHeight(10); cy1.setRadius(4.25); cy1.setPoint(2, 2);    cout<<"\nThe new location,radius and height of cy1 are\n"<<cy1    <<"Area= "<<cy1.area()<<"\nVolume= "<<cy1.volume()<<endl;    return 0;}

运行结果如下:


五. 多继承
(1) 一个类有多个直接基类的继承关系称为多继承
(2)多继承声明语法
class 派生类名:访问控制 基类名1,访问控制 基类名2,......,访问控制 基类名n
{
   数据成员和成员函数说明
}

(3)类C可以访问控制同时继承类A和类B的成员,并添加自己的成员

(4)多个基类的派生类构造函数用初始式(即初始化列表或初始化构造器)调用基类构造函数,执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
(5)一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。



(6)虚基类
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则对该基类中声明的名字进行访问时,可能产生二义性  示例如下所示:

步骤如下:

原理如下:


如果在多条将继承路径上有一个公共的基类,那么在继承路径的某处汇合点这个公共基类就会在派生类的对象中产生多个基类子对象
要使这个公共基类在派生类中只产生一个子对象,必须将这个基类声明为虚基类。
虚基类声明使用关键字 virtual

使用virtual 分析图如下所示


存储示意图如下




虚继承测试用例剖析如下 :


加virtual修饰后不同之处如下所示



总结如下:
继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
2 单继承的派生类只有一个基类。多继承的派生类有多个基类。
3 派生类对基类成员的访问由继承方式和成员性质决定。
4 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
5 C++提供虚继承机制,防止类继承关系中成员访问的二义性。
6 多继承提供了软件重用的强大功能,也增加了程序的复杂性。
7 子类自动继承父类的所有成员(包括成员变量和成员函数),除了构造函数析构函数和重载的赋值运算符。
8 所以在实现子类的构造函数时要在子类的初始化列表中调用父类的构造函数对子类从父类继承来的成员进行赋值初始化;在拷贝构造函数内也是父类成员归父类初始化,把作为子类拷贝构造函数参数的那个子类对象通过初始化列表的方式传递给父类的拷贝构造函数,然后再由子类自己,初始化子类的成员。因为析构函数不需要参数,因此在子类的析构函数中会自动调用父类的析构函数,所以不需要在子类中调用父类的析构函数(与复合类和包含类不同)只需要释放子类自身的成员指针指向的的堆内存就可以了。但在重载赋值运算符时必须要调用父类中重载的赋值运算符,对子类中属于父类的成员变量进行赋值(具体方法是“ 父类名::operator=(d)” ,d为子类中重载赋值运算符函数的参数 也就是子类对象引用类型的参数)
9 构造子类时,先执行父类的构造函数,然后再执行子类的构造函数。
10 析构子类时,先执行子类的析构函数,然后再执行父类的析构函数。


















0 0