引言:为何重载 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::cin
为 std::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;
}