重载输入输出运算符

引言:为何重载 operator<<operator>>

当类的数据成员较多时,逐一手动输出每个成员既繁琐又易出错。以如下 Point 类为例:

class Point
{
private:
    double m_x{};
    double m_y{};
    double m_z{};

public:
    Point(double x = 0.0, double y = 0.0, double z = 0.0)
        : m_x{x}, m_y{y}, m_z{z}
    {
    }

    double getX() const { return m_x; }
    double getY() const { return m_y; }
    double getZ() const { return m_z; }
};

若直接输出,需要:

Point point{ 5.0, 6.0, 7.0 };
std::cout << "Point(" << point.getX() << ", "
          << point.getY() << ", "
          << point.getZ() << ')';

可维护性不佳。虽然可以添加专门的 print() 成员函数:

void print() const
{
    std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ')';
}

print() 返回 void,无法直接插入到输出链中,例如:

std::cout << "My point is: " << point << " in Cartesian space.\n";

而重载 operator<< 后即可实现上述自然语法。


重载 operator<<

operator<< 为二元运算符,左操作数为 std::ostream&(如 std::cout),右操作数为用户自定义类型。声明如下:

friend std::ostream& operator<<(std::ostream& out, const Point& point);

实现示例:

#include <iostream>

class Point
{
private:
    double m_x{};
    double m_y{};
    double m_z{};

public:
    Point(double x = 0.0, double y = 0.0, double z = 0.0)
        : m_x{x}, m_y{y}, m_z{z}
    {
    }

    friend std::ostream& operator<<(std::ostream& out, const Point& point);
};

std::ostream& operator<<(std::ostream& out, const Point& point)
{
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ')';
    return out; // 支持链式调用
}

int main()
{
    const Point point1{ 2.0, 3.0, 4.0 };
    std::cout << point1 << '\n';
    return 0;
}

关键点

  • std::ostream 禁止拷贝,必须返回引用。
  • 返回 out 以便链式输出,如 std::cout << p1 << p2;
  • 若成员可通过公有接口访问,也可将 operator<< 实现为非友元。

重载 operator>>

std::cinstd::istream 类型,重载方式与 operator<< 类似:

friend std::istream& operator>>(std::istream& in, Point& point);

示例实现:

#include <iostream>

class Point
{
    // ... 同上
    friend std::ostream& operator<<(std::ostream& out, const Point& point);
    friend std::istream& operator>>(std::istream& in, Point& point);
};

// 输出同上

std::istream& operator>>(std::istream& in, Point& point)
{
    in >> point.m_x >> point.m_y >> point.m_z;
    return in;
}

输入问题:部分提取(Partial Extraction)

若用户输入 4.0b 5.6 7.26,提取 4.0 成功,但 b 导致后续提取失败,留下 Point(4, 0, 3) 的半初始化状态,极不可取。


防止部分提取:事务式输入

采用“全有或全无”策略,将所有数据先提取到临时变量,成功后再整体赋值:

std::istream& operator>>(std::istream& in, Point& point)
{
    double x{}, y{}, z{};
    if (in >> x >> y >> z)
        point = Point{ x, y, z }; // 全部成功才更新
    return in;
}

额外策略

  • 失败时重置对象:若提取失败,可将对象重置为默认构造值(但不再严格事务)。
  • 语义无效输入:手动设置流状态 in.setstate(std::ios_base::failbit)

示例:拒绝负数坐标

std::istream& operator>>(std::istream& in, Point& point)
{
    double x{}, y{}, z{};
    in >> x >> y >> z;
    if (x < 0.0 || y < 0.0 || z < 0.0)
        in.setstate(std::ios_base::failbit);
    point = in ? Point{ x, y, z } : Point{};
    return in;
}

小结

重载 operator<< / operator>> 使类对象与标准 I/O 无缝集成,提升可维护性与用户体验。


小测

问题 1

在如下 Fraction 类中添加重载的 operator<<operator>>

  • 避免部分提取;
  • 若用户输入分母为 0,手动置流失败位;
  • 失败时不重置 Fraction 为默认值。

要求示例程序:

int main()
{
    Fraction f1{};
    std::cout << "Enter fraction 1: ";
    std::cin >> f1;

    Fraction f2{};
    std::cout << "Enter fraction 2: ";
    std::cin >> f2;

    std::cout << f1 << " * " << f2 << " is " << f1 * f2 << '\n';
    return 0;
}

期望运行:

Enter fraction 1: 2/3
Enter fraction 2: 3/8
2/3 * 3/8 is 1/4

给定 Fraction 类框架:

#include <iostream>
#include <numeric> // for std::gcd

class Fraction
{
private:
    int m_numerator{};
    int m_denominator{};

public:
    Fraction(int numerator = 0, int denominator = 1)
        : m_numerator{numerator}, m_denominator{denominator}
    {
        reduce();
    }

    void reduce()
    {
        int gcd{ std::gcd(m_numerator, m_denominator) };
        if (gcd)
        {
            m_numerator /= gcd;
            m_denominator /= gcd;
        }
    }

    friend Fraction operator*(const Fraction& f1, const Fraction& f2);
    friend Fraction operator*(const Fraction& f1, int value);
    friend Fraction operator*(int value, const Fraction& f1);

    void print() const { std::cout << m_numerator << '/' << m_denominator; }
};

// 乘法运算符实现略

参考答案

std::ostream& operator<<(std::ostream& out, const Fraction& f)
{
    out << f.m_numerator << '/' << f.m_denominator;
    return out;
}

std::istream& operator>>(std::istream& in, Fraction& f)
{
    int num{}, den{};
    char slash{};

    if (in >> num >> slash >> den && slash == '/')
    {
        if (den == 0)
            in.setstate(std::ios_base::failbit);
        else
            f = Fraction{ num, den }; // 事务式赋值
    }
    else
        in.setstate(std::ios_base::failbit);

    return in;
}

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

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

公众号二维码

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