测验与总结

本章探讨了运算符重载、重载类型转换以及复制构造函数等相关主题。\

总结

运算符重载是函数重载的一种变体,允许为自定义类重载运算符。重载时应尽量保持其语义与原生运算符一致;若运算符对自定义类的含义不够直观,应改用具名函数。

运算符可作为普通函数、友元函数或成员函数重载。以下经验法则可帮助选择适当形式:

  • 若重载赋值(=)、下标([])、函数调用(())或成员访问(->),应实现为成员函数。
  • 若重载一元运算符,应实现为成员函数。
  • 若重载会修改左操作数的二元运算符(如 operator+=),且可行时应实现为成员函数。
  • 若重载不修改左操作数的二元运算符(如 operator+),应实现为普通函数或友元函数。

类型转换可被重载为转换函数,用于显式或隐式地将类转换为其他类型。

复制构造函数是一种特殊构造函数,用于以同类型对象初始化新对象,适用于直接/统一初始化、复制初始化(如 Fraction f = Fraction(5,3))以及按值传递或返回参数的情形。

若未手动提供复制构造函数,编译器会隐式生成一个。编译器版本采用按成员初始化,即用原对象的成员逐一初始化新对象。复制构造函数可能因优化而被省略,即使它有副作用,因此请勿依赖其一定执行。

构造函数默认被视为转换构造函数,编译器可用其将其他类型对象隐式转换为本类对象。可在构造函数前加 explicit 防止隐式转换;也可用 delete 删除包括复制构造函数、赋值运算符在内的任何函数,从而在调用时触发编译错误。

赋值运算符可被重载以支持类对象的赋值。若未手动提供,编译器将生成一个。重载的赋值运算符应始终包含自赋值检查(除非能自然处理或采用复制并交换惯用法)。

新手常混淆复制构造函数与赋值运算符的调用时机,实则简单:

  • 若需先创建新对象才能完成复制,使用复制构造函数(注:按值传递或返回对象亦属此类)。
  • 若无需创建新对象即可完成复制,使用赋值运算符。

默认情况下,编译器生成的复制构造函数与赋值运算符执行按成员初始化或赋值,即浅拷贝。若类动态分配内存,浅拷贝将导致多个对象指向同一块已分配内存,从而引发问题。此时需显式定义这些函数以执行深拷贝;更优做法是避免手动内存管理,改用标准库中的类。

测验

测验

问题 1

假设 Point 为类,point 为其对象,下列运算符应采用普通/友元函数还是成员函数重载?

a) point + point b) -point c) std::cout << point d) point = 5;

问题 2

编写名为 Average 的类,用于持续跟踪传入的所有整数的平均值。使用两个成员:

  • 一个类型为 std::int32_t,用于累计所有已见数字之和;
  • 另一个用于记录已见数字的个数。 二者相除即可得平均值。

a) 写出使下列程序得以运行的所有必要函数:

int main()
{
    Average avg{};
    std::cout << avg << '\n';

    avg += 4;
    std::cout << avg << '\n'; // 4 / 1 = 4

    avg += 8;
    std::cout << avg << '\n'; // (4 + 8) / 2 = 6

    avg += 24;
    std::cout << avg << '\n'; // (4 + 8 + 24) / 3 = 12

    avg += -10;
    std::cout << avg << '\n'; // (4 + 8 + 24 - 10) / 4 = 6.5

    (avg += 6) += 10; // 两次链式调用
    std::cout << avg << '\n'; // (4 + 8 + 24 - 10 + 6 + 10) / 6 = 7

    Average copy{ avg };
    std::cout << copy << '\n';

    return 0;
}

并产生如下输出:

0
4
6
12
6.5
7
7

b) 此类是否应提供用户自定义的复制构造函数或赋值运算符?

c) 为何使用 std::int32_t 而非 int

问题 3

从零开始编写名为 IntArray 的整数数组类(不得使用 std::arraystd::vector)。用户创建时需提供数组大小,数组应动态分配。使用 assert 防止无效数据。编写任何必要的构造函数或重载运算符,使下列程序正确运行:

#include <iostream>

IntArray fillArray()
{
    IntArray a(5);

    a[0] = 5;
    a[1] = 8;
    a[2] = 2;
    a[3] = 3;
    a[4] = 6;

    return a;
}

int main()
{
    IntArray a{ fillArray() };

    std::cout << a << '\n';

    auto& ref{ a }; // 用引用避免编译器自赋值报错
    a = ref;

    IntArray b(1);
    b = a;

    a[4] = 7;

    std::cout << b << '\n';

    return 0;
}

程序应输出:

5 8 2 3 6
5 8 2 3 6

问题 4(附加题,稍复杂)

定点数指小数位数固定的数。本测验要求实现一个保留两位小数的定点数(如 12.34、3.00、1278.99)。假设范围 −32768.99 至 32767.99,小数部分必须为两位,无精度误差,且尽量节省空间。

步骤 1

你认为应使用何种成员变量来实现两位小数的定点数?(请阅读答案再继续)

步骤 2

编写 FixedPoint2 类,实现上一步推荐的方案。若整数部分或小数部分任一(或两者)为负,则整体为负。提供重载运算符和构造函数,使下列程序运行:

#include <cassert>
#include <iostream>

int main()
{
    FixedPoint2 a{ 34, 56 };
    std::cout << a << '\n';
    std::cout << static_cast<double>(a) << '\n';
    assert(static_cast<double>(a) == 34.56);

    FixedPoint2 b{ -2, 8 };
    assert(static_cast<double>(b) == -2.08);

    FixedPoint2 c{ 2, -8 };
    assert(static_cast<double>(c) == -2.08);

    FixedPoint2 d{ -2, -8 };
    assert(static_cast<double>(d) == -2.08);

    FixedPoint2 e{ 0, -5 };
    assert(static_cast<double>(e) == -0.05);

    FixedPoint2 f{ 0, 10 };
    assert(static_cast<double>(f) == 0.1);

    return 0;
}

输出:

34.56
34.56

提示:输出时将其 static_castdouble

步骤 3

现处理小数部分越界(>99 或 <-99)情况。提供两种策略:

  • 截断(>99 置 99);
  • 进位(>99 减 100 并向整数部分加 1)。 本练习采用进位策略。

应能运行:

#include <cassert>
#include <iostream>

// 需将 testDecimal 设为 FixedPoint2 友元
bool testDecimal(const FixedPoint2& fp)
{
    if (fp.m_base >= 0)
        return fp.m_decimal >= 0 && fp.m_decimal < 100;
    else
        return fp.m_decimal <= 0 && fp.m_decimal > -100;
}

int main()
{
    FixedPoint2 a{ 1, 104 };
    std::cout << a << '\n';
    std::cout << static_cast<double>(a) << '\n';
    assert(static_cast<double>(a) == 2.04);
    assert(testDecimal(a));

    FixedPoint2 b{ 1, -104 };
    assert(static_cast<double>(b) == -2.04);
    assert(testDecimal(b));

    FixedPoint2 c{ -1, 104 };
    assert(static_cast<double>(c) == -2.04);
    assert(testDecimal(c));

    FixedPoint2 d{ -1, -104 };
    assert(static_cast<double>(d) == -2.04);
    assert(testDecimal(d));

    return 0;
}

输出:

2.04
2.04

步骤 4

添加接受 double 的构造函数。应能运行:

#include <cassert>
#include <iostream>

int main()
{
    FixedPoint2 a{ 0.01 };
    assert(static_cast<double>(a) == 0.01);

    FixedPoint2 b{ -0.01 };
    assert(static_cast<double>(b) == -0.01);

    FixedPoint2 c{ 1.9 }; // 单数小数
    assert(static_cast<double>(c) == 1.9);

    FixedPoint2 d{ 5.01 }; // 存储为 5.009999...,需四舍五入
    assert(static_cast<double>(d) == 5.01);

    FixedPoint2 e{ -5.01 }; // 需四舍五入
    assert(static_cast<double>(e) == -5.01);

    // 小数部分四舍五入为 100(整数部分需加 1)
    FixedPoint2 f{ 106.9978 }; // 应存为 base 107, decimal 0
    assert(static_cast<double>(f) == 107.0);

    // 小数部分四舍五入为 -100(整数部分需减 1)
    FixedPoint2 g{ -106.9978 }; // 应存为 base -107, decimal 0
    assert(static_cast<double>(g) == -107.0);

    return 0;
}

建议:分三步完成。先实现可直接表示的 double(a-c);再处理含舍入误差的情况(d-e);f 与 g 应借助上一步的进位逻辑处理。

步骤 5

重载 operator==operator>>、一元 operator- 和二元 operator+

应能运行:

#include <cassert>
#include <iostream>

int main()
{
    assert(FixedPoint2{ 0.75 } == FixedPoint2{ 0.75 });    // 相等为 true
    assert(!(FixedPoint2{ 0.75 } == FixedPoint2{ 0.76 })); // 相等为 false

    // 额外用例 —— 感谢读者 Sharjeel Safdar
    assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 1.98 });    // 两正,无小数溢出
    assert(FixedPoint2{ 0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 2.25 });    // 两正,小数溢出
    assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -1.98 }); // 两负,无小数溢出
    assert(FixedPoint2{ -0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -2.25 }); // 两负,小数溢出
    assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.23 } == FixedPoint2{ -0.48 });  // 第二个为负,无小数溢出
    assert(FixedPoint2{ 0.75 } + FixedPoint2{ -1.50 } == FixedPoint2{ -0.75 });  // 第二个为负,可能小数溢出
    assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.23 } == FixedPoint2{ 0.48 });   // 第一个为负,无小数溢出
    assert(FixedPoint2{ -0.75 } + FixedPoint2{ 1.50 } == FixedPoint2{ 0.75 });   // 第一个为负,可能小数溢出

    FixedPoint2 a{ -0.48 };
    assert(static_cast<double>(a) == -0.48);
    assert(static_cast<double>(-a) == 0.48);

    std::cout << "Enter a number: "; // 输入 5.678
    std::cin >> a;
    std::cout << "You entered: " << a << '\n';
    assert(static_cast<double>(a) == 5.68);

    return 0;
}

提示:

  • 步骤 4 全部提示
  • 步骤 5 全部提示

提示:
- 步骤 4 全部提示
- 步骤 5 全部提示

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"