类之间的关系(1. 使用关系和组合关系)

来源:互联网 发布:java overload 编辑:程序博客网 时间:2024/05/16 00:42

  • 类之间的关系
    • 使用关系
    • 组合关系
    • 例子
    • 参考
  • 组合关系中的构造函数和析构函数的调用顺序
  • 组合关系中调用成员的带参数的构造函数
    • 例子

类之间的关系

之前的章节我们介绍了单个类有关的知识。在程序中如果出现多个类,那么它们之间会表现出一定的关系。粗略的看,类之间有以下几种关系:

  • 使用关系
  • 组合关系
  • 继承关系

使用关系

使用关系是比较弱的关系,就是说A类使用了B类的功能(方法),在代码层面,A类可能以如下方式使用了B类:

  • B类出现在A类成员方法的参数里

    class A{    public:        void method1(B b)        {             // ...        }};
  • B类出现在A类成员方法里

    class A{    public:        void method2()        {             B b;            // ...         } };

在UML即软件开发建模语言领域,使用关系被称为依赖(Dependency)关系,其UML类图表示为:

表示A类使用了B类,注意连线是虚线。

在类之间的UML图中,有时因为依赖关系是比较弱的关系,可能就不会表示出来了(绘制虚线)。(???待确认)

使用关系是两个类之间的临时关系。上述例子中只有A类对象调用了method1或者method2方法,才会用到B类。

这就区别于类之间另一种“更强”的关系,组合关系。

组合关系

组合关系指A类的一个成员是B类的对象,换句话说,A类对象的某一个属性保存在B类对象中。组合关系也可以称为包含关系。

代码层面

class A{    private:        B b;    // ...};

在UML中,组合关系被称为关联(Association)关系,其UML类图表示为:

表示A1类的一个成员是B1类的。注意连线是实线。

相比于使用关系,组合关系是长期的关系。A类的一个成员是B类的,因此A类对象包含了一个B类对象。所以这种关系称为“组合”,组合关系也被形象地称为“有一个(has a)”关系。

聚合关系类似于组合关系,只是包含的含义更加深刻,在此不加介绍。我们目前只需要了解到组合的含义就可以了。

例子

Human类的例子

#include <iostream>#include <string> class Human {    public:        void introduce()        {            cout << ...        }    private:        string name;        ...};
  • Human类的成员函数introduce中使用了cout,而cout是输出类ostream类的对象,因此Human类使用了ostream类。

  • Human类的成员变量name是string类的对象,因此Human包含了string类。

参考

  1. Understanding UML Class Relationships

组合关系中的构造函数和析构函数的调用顺序

A类含有B类成员,那么在构造A类对象的时候也会构造成员之一的B类对象。

class B{    public:        B()        {              cout << "Construction B is called" << endl;        }        ~B()        {           cout << "Destruction B is called" << endl;        }    private:        int x;};class A{      public:        A()        {                    cout << "Construction A is called" << endl;        }        ~A()        {                    cout << "Destruction A is called" << endl;        }    private:        B b;  // A含有B类成员b};int main(){    {       A a;       cout << endl;    }    return 0;}

这里写图片描述

上述代码在main函数中将对象a创建在一对大括号中是因为大括号在程序中是一个作用域的标志,进入和退出这个区域,它里面的对象就会被创建和销毁,因此我们能及时地看到a的析构函数的调用。

可见,程序会先调用成员b的构造函数,然后再调用A类的构造函数,遵循先部分,后整体的创建顺序。对象销毁是,析构函数执行顺序相反,先调用A类的析构函数,再调用成员b的析构函数,先整体,后部分。

组合关系中调用成员的带参数的构造函数

class B{    public:        B(int x_)        {           x = x_;           cout << "Construction B is called, x = " << x << endl;        }    private:        int x;};

类B有一个带参数的构造函数。那么创建B类对象b时,需要这样B b(5);,这样才会调用该构造函数。

此时,如果A类成员b是B类类型的,那么怎样在A类的构造函数中初始化b呢?

在A类的构造函数中使用成员初始化列表来调用B类的带参数的构造函数。

class B{    public:        B(int x_)        {           x = x_;           cout << "Construction B is called, x = " << x << endl;        }    private:        int x;};class A{      public:        A(int x) : b(x)    // 构造函数的成员初始化列表,调用b的构造函数        {                    cout << "Construction A is called" << endl;        }    private:        B b;        // ...};int main(){    A a(10);  // 10是提供给成员b的构造函数的    return 0;}

这里写图片描述

构造函数的成员初始化列表是初始化类的成员的地方,可在那里指定调用成员的“某款”构造函数。

如果B类存在不带参数的构造函数,即默认构造函数,那么可以不使用成员初始化列表来初始化b。此时,创建成员b调用的就是B类默认构造函数,就像在“调用顺序”中的例子展示的那样。

注意,如果B类只含有带参数的构造函数,而没有默认构造函数,那么在A类的构造函数中一定要使用成员初始化列表来初始化b。

例子

Date类只有一个带参数的构造函数

class Date{    public:        Date(int y, int m, int d)        {            year = y;            month = m;            day = d;        }        int year, month, day;};

那么包含它的类需要在构造函数中使用成员初始化列表来初始化它。

class Human{    public:        Human(string n, ..., int y, int m, int d) : birthday(y, m, d)        {           ...        }        Date birthday;        string name;        ...};
2 0
原创粉丝点击