类的组合

Posted 夕颜缪缪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类的组合相关的知识,希望对你有一定的参考价值。

声明:本节内容摘自鸡啄米教程十五 原文链接:http://www.jizhuomi.com/software/53.html

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

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

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

组合类构造函数定义(注意不是声明)的一般形式为:

       类名::类名(形参表):内嵌对象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

// Day731_Test1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>
using namespace std;

class Point
{
public:
    Point(int xx, int yy) { X = xx; Y = yy; }
    int GetX(void) { return X; }
    int GetY(void) { return Y; }
    Point(Point &p);
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:" << myd.GetDis() << endl;
    return 0;
}

这段程序的运行结果是:
       Point拷贝构造函数被调用
       Point拷贝构造函数被调用
       Point拷贝构造函数被调用
       Point拷贝构造函数被调用
       Distance构造函数被调用
       The distance is:5

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

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

以上是关于类的组合的主要内容,如果未能解决你的问题,请参考以下文章

如何组合绑定片段而不将它们包装在 XML 文字中

48个值得掌握的JavaScript代码片段(上)

C++类的组合和聚合

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解!(转载)

片段组合在 Relay 中是如何工作的?

类的组合