继承与派生类
  知识要点
1.  掌握继承和派生的定义,派生类的定义方法。
(1) 掌握继承的两种类型:单继承和多继承。
(2) 掌握private,public,protected三种继承方式的特点。继承方式决定了基类中的成员在派生类中的属性。三种继承方式的共同点:基类的private成员在派生类中不可见。区别:对于私有继承,基类的public、protected成员在派生类中作为private成员;对于公有继承,基类的public、protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
(3) 掌握派生类中的构造函数和析构函数的使用。基类的构造函数和析构函数不能继承,所以必要时在派生类中定义自己的构造函数和析构函数。派生列的构造函数完成基类中新增数据成员和基类数据成员的初始化,基类数据成员的初始化通过基类构造函数来实现。
(4) 掌握派生类的同名覆盖规则。
(5) 掌握赋值兼容规则。基类对象可以使用公有派生类对象来代替,包括:派生类对象可以赋值给基类对象;派生类对象可以初始化基类对象的引用;基类类型指针可以指向派生类对象。
2.  掌握多重继承的概念、定义方法、多重继承派生类构造函数的执行顺序。派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类时指定的各基类顺序),在执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。
3.  掌握虚基类的概念和定义方法。在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。
典型例题分析与解答
例题1:下列对派生类的描述中,(    )是错误的。
A.  一个派生类可以作为另一个派生类的基类
B.  派生类至少有一个基类
C.  派生类的成员除了它自己的成员外,还包含了它的基类成员
D. 派生类中继承的基类成员的访问权限到派生类保持不变
答案:D
分析:一个派生类可以作为另一个派生类的基类。无论是单继承还是多继承,派生类至少有一个基类。派生类的成员除了它自己的成员外,还包含了它的基类成员。派生类中继承的基类成员的访问权限到派生类受继承方式影响的,对于私有继承,基类的public,protected成员在派生类中作为private成员;对于公有继承,基类的public,protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
例题2:派生类的对象对它的哪一类基类成员是可以访问的?(    )
  A.公有继承的基类的公有成员              B. 公有继承的基类的保护成员
  C. 公有继承的基类的私有成员              D. 保护继承的基类的公有成员
答案:A
分析:公有继承的基类的公有成员在派生类中保持公有访问权限,所以派生类对象可以访问它;公有继承的基类的保护成员在派生类中保持保护访问权限,所以派生类对象不可以访问它;基类的私有成员不能被派生到派生类中,所以派生类对象不可以访问它;保护继承的基类的公有成员在派生类中变成保护的访问权限,所以派生类对象不可以访问它。
 
例题3:关于多继承二义性的描述,(    )是错误的。
A. 派生类的多个基类中存在同名成员时,派生类对这个成员访问可能出现二义性
B.  一个派生类是从具有共同的间接基类的两个基类派生来的,派生类对该公共基类的访问可能出现二义性
C.  解决二义性最常用的方法是作用域运算符对成员进行限定
D.  派生类和它的基类中出现同名函数时,将可能出现二义性
答案:D
分析:出现二义性有两种情况:调用不同基类的相同成员时可能出现二义性;访问共同基类的成员时可能出现二义性。消除二义性的方法是采用作用域运算符。派生类和它的基类中出现同名函数时,不可能出现二义性。
 
例题4:多继承派生类构造函数构造对象时,(    )被最先调用。
A.派生类自己的构造函数      B.虚基类的构造函数
C.非虚基类的构造函数          D.派生类中子对象类的构造函数
答案:B
分析:多继承派生类构造函数构造对象时,构造函数的调顺序是:虚基类的构造函数,派生类中子对象类的构造函数, 派生类自己的构造函数。
 
例题5: C++类体系中,能被派生类继承的是(    )
A.构造函数    B.虚函数  C.析构函数    D.友元函数
答案:B
分析:C++类体系中,构造函数、析构函数和友元函数是不能被派生类继承的.
 
例题6:设有基类定义:
class Cbase
{  private: int a;
    protected: int b;
    public: int c;
};
派生类采用何种继承方式可以使成员变量b成为自己的私有成员(    )
A. 私有继承            B.保护继承
C. 公有继承            D.私有、保护、公有均可
答案:A
分析:私有继承时,基类的protected成员在派生类中作为private成员。
 
例题7:C++将类继承分为      ()          ()      两种。
答案:(1)单继承     (2)多继承
分析:派生类可以只从一个基类中派生,也可以从多个基类中派生。从一个基类中派生的继承方式称为单继承。从多个基类中派生的继承方式称为多继承。
 
例题8:派生类可以定义其_______________中不具备的数据和操作。
答案:基类
分析:派生类是从基类派生的,派生类包含了它所有基类的除构造函数、析构函数之外的所有成员,同时还拥有它自己的新成员。
 
例题9:派生类构造函数的初始化列表中包含____________________________。
答案:初始化基类数据成员、新增内嵌对象数据及新增一般成员数据所需要的全部参数。
分析:在创建派生类对象时,不仅要给派生类中说明的数据成员初始化,而且还要给基类中说明的数据成员初始化。由于构造函数不能被继承,因此,派生类的构造函数必须通过调用基类的构造函数来初始化基类中的数据成员。所以,在定义派生类的构造函数中,不仅要包含对自己数据成员进行初始化,还要包含调用基类的构造函数对基类中数据成员进行初始化。如果派生类中还有子对象时,还应包含调用对子对象初始化的构造函数。
 
例题10:在继承机制下,当对象消亡时,编译系统先执行  (1)  的析构函数,然后才执行  (2)  的析构函数,最后执行  (3)  的析构函数。
答案:(1)派生类    (2)派生类中子对象类   (3)基类
分析:派生类的析构函数的执行次序与构造函数正好相反,先调用派生类的析构函数,再调用派生类中子对象类的析构函数,最后调用基类的析构函数。
 
例题11:设有以下类的定义:
class A              class B: protected A        class C: private B
{  int  A1;            {  int b1;              {    int c1;
protected:  int A2;      protected: int b2;        protected: int c2;
public:  int A3;        public: int b3;          public: int c3;
};                      };                    };
请按访问权限写出派生类C中具有的成员。
私有成员:析构函数的定义            (1)                         
保护成员:            (2)                         
公有成员:            (3)                         
答案:(1)c1、b2、b3、A2、A3    (2)c2    (3)c3
分析:B类有它自己的私有成员b1、保护成员b2和公有成员有b3,另外B类是以保护方式从A类中派生出来的,所以A类保护成员A2和公有成员A3在B类中都变成保护类型的;C类有自己的私有成员c1、保护成员c2和公有成员有c3,C类是以私有方式从B类中派生出来的,所以B类中的b2、b3、A2和A3在C类中都变成私有的访问方式。
 
例题12:指出并改正下面程序中的错误。
#include<iostream.h>
class Point
{  int x,y;
public:
    Point(int a=0,int b=0) {x=a; y=b;}
    void move(int xoffset,int yoffset) {x+=xoffset; y+=yoffset;}
    int getx() {return x;}
    int gety() {return y;}
};
class Rectangle:protected Point
{    int length,width;
  public:
    Rectangle(int x,int y,int l,int w):Point(x,y)
        {  length=l;width=w;}
    int getlength(){return length;}
    int getwidth(){return width;}
};
void main()
{ Rectangle r(0,0,8,4);
r.move(23,56);
cout<&()<<","<&()<<","
<&length()<<","<&width()<<endl;