C++类的复合和继承关系(C++继承类和封闭类的关系)
在 C++ 中,类和类之间有两种基本关系:复合关系和继承关系。
复合关系也称为“has a”关系或“有”的关系,表现为封闭类,即一个类以另一个类的对象作为成员变量。如上节中 CStudent 类的例子,每个 CStudent 对象都“有”一个 string 类的成员变量 name,代表姓名。
继承关系也称为“is a”关系或“是”的关系,即派生类对象也是一个基类对象。如在上节的程序中,CUndergraduateStudent 类(代表本科生)继承了 CStudent 类(代表学生)。因为本科生也是学生,因此可以说,每一个 CUndergraduateStudent 类的对象也是一个 CStudent 类的对象。
在设计两个有关系的类时要注意,并非两个类有共同点,就可以让它们成为继承关系。让类 B 继承类 A,必须满足“类 B 所代表的事物也是类 A 所代表的事物”这个命题从逻辑上是成立的。例如,写一个平面上的点类 CPoint::
如果写了一个 CMan 类代表男人,后来发现又需要一个 CWoman 类代表女人,仅仅因为 CWoman 类和 CMan 类有共同之处,就让 CWoman 类从 CMan 类派生而来,同样也是不合理的。因为“一个女人也是一个男人”从逻辑上不成立。
但是让 CWoman 类包含 CMan 类成员对象就更不合适了。
此时正确的做法应该是概括男人和女人的共同特点,编写一个 CHuman 类,代表“人”,然后 CMan 类和 CWoman 类都从 CHuman 类派生。
有时,复合关系也不一定都是通过封闭类实现的,尤其当类 A 中有类 B,类 B 中又有类 A 的情况。
假设要编写一个小区养狗管理程序,该程序需要一个“主人”类,还需要一个“狗”类。狗是有主人的,主人也有狗。假定狗只有一个主人,但一个主人可以有最多 10 条狗。该如何处理“主人”类和“狗”类的关系呢?下面是一种直观的写法:
避免循环定义的方法是在一个类中使用另一个类的指针,而不是对象作为成员变量。例如下面的写法:
这种写法的思想是:当一个 CMaster 对象养了一条狗时,就用 new 运算符动态分配一个 CDog 类的对象,然后在 dogs 数组中找一个元素,让它指向动态分配的 CDog 对象。
这种写法还是不够好。问题出在 CDog 对象中包含了 CMaster 对象。在多条狗的主人相同的情况下,多个 CDog 对象中的 CMaster 对象都代表同一个主人,这造成了没有必要的冗余:一个主人用一个 CMaster 对象表示足矣,没有必要对应于多个 CMaster 对象。
而且,在一对多这种情况下,当主人的个人信息发生变化时,就需要将与其对应的、位于多个 CDog 对象中的 CMaster 成员变量 m 都找出来修改,这毫无必要,而且非常麻烦。
正确的写法应该是为“狗”类设一个“主人”类的指针成员变量,为“主人”类设一个“狗”类的对象数组。如下所示:
实际上,每个主人未必都养 10 条狗,因此出于节省空间的目的,在 CMaster 类中设置 CDog 类对象的指针数组,而不是对象数组,也是一种好的写法。如下所示:
复合关系也称为“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”,两个类之间是“知道”关系。
所有教程
- socket
- Python基础教程
- C#教程
- MySQL函数
- MySQL
- C语言入门
- C语言专题
- C语言编译器
- C语言编程实例
- GCC编译器
- 数据结构
- C语言项目案例
- C++教程
- OpenCV
- Qt教程
- Unity 3D教程
- UE4
- STL
- Redis
- Android教程
- JavaScript
- PHP
- Mybatis
- Spring Cloud
- Maven
- vi命令
- Spring Boot
- Spring MVC
- Hibernate
- Linux
- Linux命令
- Shell脚本
- Java教程
- 设计模式
- Spring
- Servlet
- Struts2
- Java Swing
- JSP教程
- CSS教程
- TensorFlow
- 区块链
- Go语言教程
- Docker
- 编程笔记
- 资源下载
- 关于我们
- 汇编语言
- 大数据
- 云计算
- VIP视频