私有继承 和 包含类

来源:互联网 发布:软件开发阶段 英文 编辑:程序博客网 时间:2024/06/05 16:00

has-a关系

 

包含对象成员的类
class student
{private:
    std::string name;             // use a string object for name
    std::valarray<double> scores; // use a valarray<double> object for scores
    ...
 public:
    student():name("null student"),scores(){}
    student(const char * str,const double * pd,int n)
           :name(str),scores(pd,n){}
    explicit student(int n):name("nully"),scores(n){}
    ...
};

 

   上述类将数据成员声明为私有的,意味着student类的成员函数可以使用string和valarray<double>类的公有接口来访问和修改name和scores对象,但在类外面不能这样做,而只能通过student类的公有接口访问name和scores。这种情况,通常被描述为student类获得了其成员对象的实现,但没有继承接口。
    获得接口是is-a关系的组成部分,而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。

 

student(const char * str,const double * pd,int n)
       :name(str),scores(pd,n){}


因为该构造函数初始化的是成员对象,而不是继承的对象,所以在初始化列表中使用的是成员名,而不是类名。

 

其中初始化列表中的每一项都调用与之匹配的构造函数,即name(str)调用string(const char*)构造函数,scores(pd,n)调用valarray<double>(const double*,int)构造函数。

 

初始化顺序
    当初始化列表中包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。就上述构造函数,name成员仍将首先被初始化,因为在类定义中它首先被声明。

(如果代码中使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常的重要了)

 

explicit student(int n):name("nully"),scores(n){}

 


在上述构造函数中,第一个参数表示数组的元素个数,而不是数组中的值,因此将一个构造函数用作int到student的转换函数是没有意义的,所以使用explicit关闭隐式转换。否者可以编写如下所示的代码:
student doh("homer",10); //store "homer",create array of 10 elements
doh=5; //reset name to "nully",reset to empty array of 5 elements

 

私有继承
    使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以再派生类的成员函数中使用它们。
    私有继承提供的特征与包含相同:获得实现,但不获得接口。

使用关键字private来定义类(private实际上是默认值,省略访问限定符也将导致私有继承)

student 类范例(新版本)
class student:private std::string,private std::valarray<double>
{
 public:
   ...
};

 

使用多个基类的继承被称为多重继承(multiple inheritance,MI)。

    包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。使用术语子对象(subobject)来表示通过继承或包含添加的对象。所以新的student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员。

 

初始化基类组件
隐式的继承组件而不是成员对象将影响代码的编写,不能使用name和scores来描述对象,而必须使用用于公有继承的技术:
student(const char * str,const double * pd,int n)
       :std::string(str),std::valarray<double>(pd,n){}

 

访问基类的方法
使用私有继承时,只能在派生类的方法中使用基类的方法:
(使用类名和作用域解析操作符来调用基类方法)
double student::average()const
{
 if(std::valarray<double>::size() > 0)
    return std::valarray<double>::sum()/std::valarray<double>::size();
 else
    return 0;
}

 

访问基类对象
    例如student类的包含版本,可以用方法实现返回string对象成员name;但使用私有继承时,该string对象没有名称,如何访问内部的string对象。
    使用强制类型转换。由于student类是从string类派生而来的,因此可以通过强制类型转换,将student对象转换为string对象;结果为继承而来的string对象。再使用*this指针来得到调用方法本身。

const string & student::name()const
{ return (const string &) *this; }

 

上述方法返回一个引用,该引用指向用于调用该方法的student对象中的继承而来的string对象。

 

访问基类的友元函数
通过显示地转换为基类,来调用正确的函数。例如,对于下面的友元函数定义:
ostream & operator<<(ostream & os,const student & stu)
{
  os<<"scores for "<<(const string &)stu<<":/n";
  ...
}

如果plato是一个student对象,则cout<<plato;将调用上述函数,stu将是指向plato的引用,而os将是指向cout的引用。下面的代码:
os<<"scores for "<<(const string &)stu<<":/n";
显示地将stu转换为string对象引用,这与operator<<(ostream &,const string &)函数匹配。

    引用stu不会自动的转换为string引用。根本原因在于,在私有继承中,在不进行显示类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。


(即使这个例子中使用的是公有继承,也必须使用显示类型转换。原因之一是,如果不使用类型转换,代码(so<<stu)将与友元函数原型匹配,从而导致递归调用。另一个原因是,该类使用多重继承,编译器无法确定应该转换成那个基类,如果两个基类都提供了函数operator<<()的话。)

 

使用包含还是私有继承
    对于使用包含来说,它易于理解。类声明中包含表示被包含类的显示命名对象,代码可以通过名称引用这些对象,而使用继承将使关系更抽象。其次,继承会引起许多问题,尤其从多个基类继承时,可能必须处理许多问题,例如包含同名方法的独立的基类,或共享祖先的独立基类。而使用包含则不大可能会遇到这样的麻烦。另外,包含能够包括多个同类的子对象。如果某个类需要3个string对象,可以使用包含声明3个独立的string成员。而继承则只能使用一个这样的对象(当对象都没有名称时,将难以区分)。
    但私有继承所提供的特性却比包含多。例如,假设类包含保护成员(可以是数据成员,也可以是成员函数),则这样的成员在派生类中是可用的,但在继承层次结构外是不可用的。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承得到的将是派生类,因此它能够访问保护成员。
    另一种需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。

(通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承)