在我们对现实中的某些事物抽象成类时,可能会形成很复杂的类,为了更简洁的进行软件开发,我们经常把其中相对比较独立的部分拿出来定义成一个个简单的类,这些比较简单的类又可以分出更简单的类,最后由这些简单的类再组成我们想要的类。比如,我们想要创建一个计算机系统的类,首先计算机由硬件和软件组成,硬件又分为CPU、存储器等,软件分为系统软件和应用软件,如果我们直接创建这个类是不是很复杂?这时候我们就可以将CPU写一个类,存储器写一个类,其他硬件每个都写一个类,硬件类就是所有这些类的组合,软件也是一样,也能做成一个类的组合。计算机类又是硬件类和软件类的组合。

类的组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。简单说,一个类中有若干数据成员是其他类的对象。以前我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、float类型的或结构体类型的,现在我们知道了,数据成员也可以是类类型的。

如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化。

其实,组合(有时候叫聚合)是将一个对象(部分)放到另一个对象里(组合),它是一种 has-a 的关系。

例如下例,FordTaurus是由Engine,Transmission,InstrumentPanel,等等组合而成.也就是说,FordTaurus含有一个Engine.(Engine 是FordTaurus的Part-of):

#include "stdafx.h"#include <iostream>
using namespace std;class Engine {
public:virtual void start();
};void Engine::start()
{cout << "starting Engine\n";
}class FordTaurus {
public:virtual void start();
protected:Engine engine_;    // An Engine is part of a FordTaurus
};
void FordTaurus::start()
{cout << "starting FordTaurus\n";engine_.start();
}int main()
{FordTaurus taurus;taurus.start();return 0;
}

输出:

starting FordTaurus
starting Engine

有时候开发者在他们该使用组合的时候错误的使用继承(kind-of),例如,他们可能使FordTaurus从Engine继承,这是把Kind-of和Part-of给搞混乱了.

一个类含多个类的对象时,组合类构造函数定义(注意不是声明)的一般形式为:

类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{
类的初始化
}

其中,“内嵌对象1(形参表),内嵌对象2(形参表),...”成为初始化列表,可以用于完成对内嵌对象的初始化。其实,一般的数据成员也可以这样初始化,就是把这里的内嵌对象都换成一般的数据成员,后面的形参表换成用来的初始化一般数据成员的变量形参,比如,Point::Point(int xx, int yy):X(xx),Y(yy) { },这个定义应该怎么理解呢?就是我们在构造Point类的对象时传入实参初始化xx和yy,然后用xx的值初始化Point类的数据成员X,用yy的值初始化数据成员Y。

声明一个组合类的对象时,不仅它自身的构造函数会被调用,还会调用其内嵌对象的构造函数。那么,这些构造函数的调用是什么顺序呢?首先,根据前面说的初始化列表,按照内嵌对象在组合类的声明中出现的次序,依次调用内嵌对象的构造函数,然后再执行本类的构造函数的函数体。比如下面例子中对于Distance类中的p1和p2就是先调用p1的构造函数,再调用p2的构造函数。因为Point p1,p2;是先声明的p1后声明的p2。最后才是执行Distance构造函数的函数体。

如果声明组合类的对象时没有指定对象的初始值的话,就会自动调用无形参的构造函数,构造内嵌对象时也会对应的调用内嵌对象的无形参的构造函数。析构函数的执行顺序与构造函数正好相反。
下面一个类的组合的例子,其中,Distance类就是组合类,可以计算两个点的距离,它包含了Point类的两个对象p1和p2。

#include "stdafx.h"#include <iostream>
using namespace std;class Point
{
public:Point(int xx,int yy)   { X=xx; Y=yy; } //构造函数Point(Point &p);int GetX(void)     { return X; }        //取X坐标int GetY(void)     { return Y; } //取Y坐标
private:int X,Y; //点的坐标
};Point::Point(Point &p)
{X = p.X;Y = p.Y;cout << "Point拷贝构造函数被调用" << endl;
}class Distance
{
public:Distance(Point a,Point b); //构造函数double GetDis()   { return dist; }
private:Point  p1,p2;double dist;               // 距离
};
// 组合类的构造函数
Distance::Distance(Point a, Point b):p1(a),p2(b)
{cout << "Distance构造函数被调用" << endl;double x = double(p1.GetX() - p2.GetX());double y = double(p1.GetY() - p2.GetY());dist = sqrt(x*x + y*y);return;
}int _tmain(int argc, _TCHAR* argv[])
{Point myp1(1,1), myp2(4,5);Distance myd(myp1, myp2);cout << "The distance is:";cout << myd.GetDis() << endl;return 0;
}

输出结果:

分析下这个程序,首先生成两个Point类的对象,然后构造Distance类的对象myd,最后输出两点的距离。Point类的拷贝构造函数被调用了4次,而且都是在Distance类构造函数执行之前进行的,在Distance构造函数进行实参和形参的结合时,也就是传入myp1和myp2的值时调用了两次,在用传入的值初始化内嵌对象p1和p2时又调用了两次。两点的距离在Distance的构造函数中计算出来,存放在其私有数据成员dist中,只能通过公有成员函数GetDis()来访问。

类组合还有一种特殊情况,就是两个类可能相互包含,即类A中有类B类型的内嵌对象,类B中也有A类型的内嵌对象。我们知道,C++中,要使用一个类必须在使用前已经声明了该类,但是两个类互相包含时就肯定有一个类在定义之前就被引用了,这时候怎么办呢?就要用到前向引用声明了。前向引用声明是在引用没有定义的类之前对该类进行声明,这只是为程序声明一个代表该类的标识符,类的具体定义可以在程序的其他地方,简单说,就是声明下这个标识符是个类,它的定义你可以在别的地方找到。

比如,类A的公有成员函数f的形参是类B的对象,同时类B的公有成员函数g的形参是类A的对象,这时就必须使用前向引用声明:

class B; //前向引用声明
class A
{
public:
void f(B b);
};
class B
{
public:
void g(A a);
};

这段程序的第一行给出了类B的前向引用声明,说明B是一个类,它具有类的所有属性,具体的定义在其他地方。

C++之类的组合(聚合)相关推荐

  1. UML类图几种关系的总结,泛化 = 实现 组合 聚合 关联 依赖

    在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Compositi ...

  2. 火车头采集器文章组合聚合

    目录 火车头采集文章组合聚合的概念 为什么要使用火车头采集seo文章组合 SEO文章原创度识别概念 SEO文章SEO优化技巧 一.seo文章组合的概念: seo文章只符合搜索引擎优化和用户体验,由2- ...

  3. 软件设计原则SOLID+组合聚合+迪米特原则(附代码讲解)

    SOLID是五大设计原则的首字母简写,最早出现于出自Robert Martin(罗伯特. 马丁)的<架构整洁之道>第三章设计原则.他们分别是 single Responsibility P ...

  4. 面向对象设计原则七:组合/聚合复用原则

    组合/聚合复用原则(LSP) 定义:优先使用组合,使系统更灵活,其次才考虑继承,达到复用的目的. 重用的方式: 继承.组合.聚合 解释说明: 继承:在我们想复用代码时,我们一般会优先想到继承,但是具有 ...

  5. 『设计模式』一句话教你分清楚UML组合聚合和联系!

    23种设计模式+额外常用设计模式汇总 (持续更新) 组合:组合后的实体消失,则所有构成实体的部件都无意义,可以理解为不能独立存在 定义: 与聚合相比,组合描述的是这样的关联关系,部分离开整体后就没有实 ...

  6. c++组合 聚合 关联

    组合和聚合区别(不能脱离整体 能脱离整体) 组合:(表示两个对象之间是整体和部分的强关系,部分的生命周期不能超越整体.如人和脑袋) 比如A类中包含B类的一个引用b,当A类的一个对象消亡时,b这个引用所 ...

  7. java 组合 聚合_关联,组合和聚合-用Java实现

    我对关联,聚合和组成有些困惑.即使大量的网站和论坛讨论了此主题,但在阅读其中一些内容后,我变得更加困惑. 我想知道以下是否正确: 1.)聚集-如果整体被破坏,将存在.例如,一个引擎可以有或没有汽车. ...

  8. 泛化 实现 组合 聚合 关联 依赖

  9. 继承、实现、依赖、关联、聚合、组合的联系与区别

    继承 指的是一个类(称为子类.子接口)继承另外的一个类(称为父类.父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系:在Java中此类关系通过关键字extend ...

  10. UML关系(泛化,实现,依赖,关联(聚合,组合))

    UML的构造快包含3种: (1) 事物(4种):结构事物,行为事物,分组事物,注释事物 (2) 关系(4种):泛化关系,实现关系,依赖关系,关联关系 (3) 图(10种):用例图,类图,对象图,包图, ...

最新文章

  1. Scratch等级考试(一级)模拟题
  2. linux libfcmain.so,BabyLinux制作过程详解
  3. php 连接数据库 pod,PHP PDO类解决数据库连接问题
  4. DBDesigner 4 与 MySql 5 不能连接主要是驱动的原因
  5. 目标检测的二十年发展史—从传统方法到深度学
  6. 【gRPC基础知识】快速部署
  7. Q窗口操作函数(窗口最大化,全屏,隐藏最大化最小化按钮)
  8. javascript优化--01高质量编码
  9. src与href区别
  10. Pikachu实验过程重现(Burp suit的具体介绍)
  11. Nginx二级目录反向代理网站
  12. SVN如何批量忽略文件和文件夹
  13. 【301】怪诞行为学-可预测的非理性
  14. SQL 升序、降序排列
  15. 为知笔记如何一键收藏微信文章?
  16. re匹配截至到第一个中文_Python中的正则表达式(re模块)!非常重要!
  17. 【FL论文阅读】Communication-Efficient Learning of Deep Networks from Decentralized Data
  18. jsd2205-csmall-passport(Day13)
  19. win10 UWP Controls by function
  20. python linux 时间格式化,Python中的时间日期转换

热门文章

  1. linux var是什么文件夹,linux中/etc与/var目录,各是什么意思?这两个目录下的文件有什么特点?...
  2. Linux技术资料锦集及命令大全,让你从新手变高手
  3. 【产品理解】一些对于产品的理解
  4. C语言测试。自己实现scandir 函数
  5. sh命令 /Linux中执行shell脚本的4种方法总结
  6. android锁屏音乐歌词的实现,在锁屏界面显示 Apple Music 的歌词
  7. Word2016中的自动编号问题
  8. 怎样将word中的图片另存为jpg格式的图片
  9. Hadoop大数据生态系统笔记
  10. react的路由守卫,路由鉴权,路由守卫的封装