首页 > 编程笔记 > C++笔记 阅读:2,672

C++类的复合和继承关系(C++继承类和封闭类的关系)

C++ 中,类和类之间有两种基本关系:复合关系和继承关系。

复合关系也称为“has a”关系或“有”的关系,表现为封闭类,即一个类以另一个类的对象作为成员变量。如上节中 CStudent 类的例子,每个 CStudent 对象都“有”一个 string 类的成员变量 name,代表姓名。

继承关系也称为“is a”关系或“是”的关系,即派生类对象也是一个基类对象。如在上节的程序中,CUndergraduateStudent 类(代表本科生)继承了 CStudent 类(代表学生)。因为本科生也是学生,因此可以说,每一个 CUndergraduateStudent 类的对象也是一个 CStudent 类的对象。

在设计两个有关系的类时要注意,并非两个类有共同点,就可以让它们成为继承关系。让类 B 继承类 A,必须满足“类 B 所代表的事物也是类 A 所代表的事物”这个命题从逻辑上是成立的。例如,写一个平面上的点类 CPoint::
class CPoint{
    double x, y;  //点的坐标
};
又要写一个圆类 CCircle。CCircle 类有圆心,圆心也是平面上的一点,因而 CCircle 类和 CPoint 类似乎有相同的成员变量。如果因此就让 CCircle 类从 CPoint 类派生而来,即采用如下写法:
class CCircle: public CPoint{
    double radius;  //半径
};
是不正确的。因为,“圆也是点”这个命题是不成立的。这个错误不但初学者常犯,甚至很多知名教材也以此作为继承的例子。正确的做法是使用“has a”关系,即在 CCircle 类中引入 CPoint 成员变量,代表圆心:
class CCircle
{
    CPoint center;  //圆心
    double radius;  //半径
}
这样,从逻辑上来说,每一个“圆”对象都包含(有)一个“点”对象,这个“点”对象就是圆心——这非常合理。

如果写了一个 CMan 类代表男人,后来发现又需要一个 CWoman 类代表女人,仅仅因为 CWoman 类和 CMan 类有共同之处,就让 CWoman 类从 CMan 类派生而来,同样也是不合理的。因为“一个女人也是一个男人”从逻辑上不成立。

但是让 CWoman 类包含 CMan 类成员对象就更不合适了。

此时正确的做法应该是概括男人和女人的共同特点,编写一个 CHuman 类,代表“人”,然后 CMan 类和 CWoman 类都从 CHuman 类派生。

有时,复合关系也不一定都是通过封闭类实现的,尤其当类 A 中有类 B,类 B 中又有类 A 的情况。

假设要编写一个小区养狗管理程序,该程序需要一个“主人”类,还需要一个“狗”类。狗是有主人的,主人也有狗。假定狗只有一个主人,但一个主人可以有最多 10 条狗。该如何处理“主人”类和“狗”类的关系呢?下面是一种直观的写法:
class CDog;
class CMaster  //主人
{
    CDog dogs[10];
    int dogNum;  //狗的数量
};
class CDog
{
    CMaster m;
};
这种写法是无法通过编译的。因为尽管提前对 CDog 类进行了声明,但编译到第 4 行时,编译器还是不知道 CDog 类的对象是什么样的,所以无法编译定义 dog 对象的语句。而且这种“人中有狗,狗中有人”的做法导致了循环定义。

避免循环定义的方法是在一个类中使用另一个类的指针,而不是对象作为成员变量。例如下面的写法:
class CDog;
class CMaster
{
    CDog* dogs[10];
    int dogNum;  //狗的数量
};
class CDog
{
    CMaster m;
};
上面这种写法在第 4 行定义了一个 CDog 类的指针数组作为 CMaster 类的成员对象。指针就是地址,大小固定为 4 个字节,所以编译器编译到此时不需要知道 CDog 类是什么样子。

这种写法的思想是:当一个 CMaster 对象养了一条狗时,就用 new 运算符动态分配一个 CDog 类的对象,然后在 dogs 数组中找一个元素,让它指向动态分配的 CDog 对象。

这种写法还是不够好。问题出在 CDog 对象中包含了 CMaster 对象。在多条狗的主人相同的情况下,多个 CDog 对象中的 CMaster 对象都代表同一个主人,这造成了没有必要的冗余:一个主人用一个 CMaster 对象表示足矣,没有必要对应于多个 CMaster 对象。

而且,在一对多这种情况下,当主人的个人信息发生变化时,就需要将与其对应的、位于多个 CDog 对象中的 CMaster 成员变量 m 都找出来修改,这毫无必要,而且非常麻烦。

正确的写法应该是为“狗”类设一个“主人”类的指针成员变量,为“主人”类设一个“狗”类的对象数组。如下所示:
class CMaster;
classCDog
{
    CMaster* pm;
};
class CMaster
{
    CDog dogs[10];
    int dogNum;
};
这样,主人相同的多个 CDog 对象,其 pm 指针都指向同一个 CMaster 对象。

实际上,每个主人未必都养 10 条狗,因此出于节省空间的目的,在 CMaster 类中设置 CDog 类对象的指针数组,而不是对象数组,也是一种好的写法。如下所示:
class CMaster
{
    CDog* dogs[10];
    int dogNum;
};
有的教材将类 A 的成员变量是类 B 的指针这种情况称为“类 A 知道类 B”,两个类之间是“知道”关系。