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

本文探讨了类之间的关系,重点讲述了使用关系(依赖关系)和组合关系(关联关系)。使用关系表现为一个类使用另一个类的功能,是临时性的。而组合关系则是一个类包含另一个类的对象作为其成员,形成长期的关系。文中通过UML类图进行说明,并提供了代码示例,解释了构造函数和析构函数的调用顺序以及如何在组合关系中调用成员的带参数构造函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类之间的关系

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

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

使用关系

使用关系是比较弱的关系,就是说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;
        ...
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值