Cpp学习26 对象关系;继承

铁名_IronName Lv5

对象组合有两种基本子类型:组合和聚合。composition and aggregation

Composition 组合

要构成一个组合 ,一个物体和一个部分必须具有以下关系:

  • 该部分(成员)是对象(类)的一部分。

  • 一个部件(成员)一次只能属于一个对象(类)。

  • 该部分(成员)的存在由对象(类)管理。

  • 该部分(成员)并不知道对象(类)的存在。

Aggregation  聚合

要符合聚合的定义,一个整体对象及其各个部分必须具有以下关系:

  • 该部分(成员)是对象(类)的一部分。

  • 部件(成员)可以(如果需要)同时属于多个对象(类)。

  • 该部分(成员)的存在 并非 由对象(类)管理。

  • 该部分(成员)并不知道对象(类)的存在。

在聚合中,我们也把各个部分作为成员变量添加进去。然而,这些成员变量通常是引用或指针,指向在类作用域之外创建的对象。

std::reference_wrapper

它像引用一样工作,但也允许赋值和复制,因此它与 std::vector 等列表兼容。
好消息是,你其实不需要了解它的工作原理就能使用它。你只需要知道三件事:

  1. std::reference_wrapper 位于<functional>。
  2. 当您创建 std::reference_wrapper 包装的对象时,该对象不能是匿名对象(因为匿名对象具有表达式作用域,这将导致引用悬空)。
  3. 当你想从 std::reference_wrapper 中取回你的对象时,可以使用 get() 成员函数。

Association  关联

任何能够将两个对象链接在一起的数据都足够了。

Dependencies 依赖

当一个对象调用另一个对象的功能来完成特定任务时,就会产生依赖关系 。

容器类

容器类是一种用于保存和组织另一种类型(可以是另一个类,也可以是基本类型)多个实例的类。(例如 std::array 或 std::vector)

 值容器组合 ,它存储所持有对象的副本(因此负责创建和销毁这些副本)。 引用容器聚合 ,它存储指向其他对象的指针或引用(因此不负责创建或销毁这些对象)。

(然后教程写了个容器类IntArray出来)

std::initializer_list

当编译器遇到初始化列表时,它会自动将其转换为 std::initializer_list 类型的对象。因此,如果我们创建一个接受 std::initializer_list 参数的构造函数,就可以使用该初始化列表作为输入来创建对象。
std::initializer_list 位于 <initializer_list> 标头中。
总之是让自己写的容器类可以用列表初始化的方法。


继承

C++中的继承发生在类之间。在继承(is-a)关系中,被继承的类称为父类 、 基类超类 ,而继承的类称为子类 、 派生类子类 。

将 BaseballPlayer 类设为派生类

1
2
3
4
5
6
7
8
9
10
11
class BaseballPlayer : public Person
{
public:
double m_battingAverage{};
int m_homeRuns{};

BaseballPlayer(double battingAverage = 0.0, int homeRuns = 0)
: m_battingAverage{battingAverage}, m_homeRuns{homeRuns}
{
}
};

继承基类意味着我们无需在派生类中重新定义基类的信息。通过继承,我们会自动获得基类的成员函数和成员变量,然后只需添加所需的其他函数或成员变量即可。这不仅节省了工作量,还意味着如果我们更新或修改基类(例如添加新函数或修复错误),所有派生类都会自动继承这些更改!

派生类的构造顺序

C++ 分阶段构建派生类,从最基类(位于继承树顶部)开始,到最子类(位于继承树底部)结束。每个类构建完成后,都会调用该类的相应构造函数来初始化该类的相应部分。

派生类的构造函数和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
class Derived: public Base
{
public:
double m_cost {};

Derived(double cost=0.0, int id=0)
: Base{ id } // Call Base(int) constructor with value id!
, m_cost{ cost }
{
}

double getCost() const { return m_cost; }
};

公共成员可以被任何人访问。私有成员只能由同一类的成员函数访问。请注意,这意味着派生类不能直接访问基类的私有成员!派生类需要使用访问函数来访问基类的私有成员。

继承和访问说明符

受保护的访问说明符

protected 访问修饰符允许成员所属的类、友元类和派生类访问该成员。但是,受保护的成员不能从类外部访问。

最佳实践

优先考虑 private 成员 而非 protected 成员。

不同类型的继承及其对继承权的影响

基类中为 public 或 protected 的成员在派生类中可能会改变其访问修饰符。

最佳实践

除非有特殊原因,否则请使用公共继承。

public 继承时,成员权限:原public, protected都不变,原private无法访问。
protected 继承时,成员权限:原protected, protected都变为protected,原private无法访问。
private 继承时,成员权限:原protected, protected都变为private,原private无法访问。

向派生类添加新功能

正常写就是了。

调用继承的函数 和 重写行为

如果基类提供的行为足够,我们直接使用基类的行为。
要修改基类中定义的函数在派生类中的工作方式,只需在派生类中重新定义该函数即可。

基于原函数扩展:

1
2
3
4
5
6
7
8
9
10
11
class Derived: public Base
{
public:
Derived() { }

void identify() const
{
std::cout << "Derived::identify()\n";
Base::identify(); // note call to Base::identify() here
}
};

当我们尝试调用基类中的友元函数时,例如 operator<< 会遇到一个棘手的问题。因为基类的友元函数实际上并不属于基类本身,所以使用作用域解析限定符是行不通的。因此,我们需要一种方法,让 Derived 类暂时看起来像 Base 类,以便能够调用正确的函数版本。(使用 static_cast

通过将 using 声明 using Base::print; 放在 Derived 中,Base::print(int) 会被优先于 Derived::print(double) 被选中。

1
2
3
4
5
6
class Derived: public Base
{
public:
using Base::print; // make all Base::print() functions eligible for overload resolution
void print(double) { std::cout << "Derived::print(double)"; }
};

隐藏继承的功能

在派生类中,可以通过更改相关的访问说明符来隐藏基类中存在的功能,使其无法通过派生类访问。

1
2
3
4
class Derived : public Base
{
private:
using Base::m_value;

还可以将派生类中的成员函数标记为已删除,这样可以确保它们完全无法通过派生对象调用。
(函数:我一定得死吗?)

多重继承

C++ 提供了多重继承的功能。 多重继承允许派生类从多个父类继承成员。

混入类 (mixin,也拼作“mix-in”)是一个可以被其他类继承的小型类,用于向其他类添加属性。名称中的“mixin”表明该类旨在与其他类混合使用,而不是独立实例化。

最佳实践

除非其他选择会导致系统更加复杂,否则应避免多重继承。

  • 标题: Cpp学习26 对象关系;继承
  • 作者: 铁名_IronName
  • 创建于 : 2026-08-14 16:03:44
  • 更新于 : 2026-08-14 17:19:23
  • 链接: https://blog.ironname.top/2026/Cpp/Cpp学习26/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论