用 cout 输出对象
运算符重载实现
我们先观察一个简化的 Time
类实现:
/* Time.h */
#include <iostream>
using std::cout;
using std::endl;
class Time
{
private:
int hour;
int minute;
int second;
public:
Time(int h = 0, int m = 0, int s = 0) : hour(h), minute(m), second(s) {}
void Show() const { cout << hour << ":" << minute << ":" << second << endl; }
};
/* Time.cpp */
#include "Time.h"
int main()
{
Time t(12, 30, 20);
t.Show();
return 0;
}
当前实现需要通过 t.Show()
显式调用输出方法,这种设计违背了面向对象编程的直观性原则。理想的使用方式应当支持标准流输出语法:
cout << t; // 期望输出:12:30:20
直接尝试该语法会导致编译错误,因为编译器无法自动推导自定义类型的输出格式。为此我们需要为 std::ostream
重载 <<
运算符。考虑到运算符函数需要访问类的私有成员,必须将其声明为友元函数:
friend void operator<<(std::ostream &os, const Time &t)
{
os << t.hour << ":" << t.minute << ":" << t.second;
}
此时即可通过标准输出流直接操作对象:
cout << t; // 正确输出:12:30:20
链式调用问题
在实际使用中我们发现该实现无法支持链式输出,下列代码将引发编译错误。这会严重影响代码的可组合性,违背运算符重载应保持原生语义的原则:
cout << t << " Have a good day, Adehit!"; // 编译错误
编译后出现了错误信息 error: invalid operands of types 'void' and 'const char [26]' to binary 'operator<<'
。即当前运算符函数返回void
类型,导致后续输出操作缺乏有效的流对象。
通过类型推导工具可以验证运算符的预期行为:
#include <typeinfo>
using std::cout;
int main()
{
cout << (typeid(decltype(cout << "")).name()); // 输出So.(即ostream&类型,不同编译器输出结果可能不同)
}
原生流运算符始终返回流引用以支持链式调用。同理,cout << t
的返回值必须保持流对象的连续性。需要注意的一个小细节是,我们需要返回 ostream
的引用,若返回非引用类型,每次调用都会产生流对象的副本,这不仅违反流对象不可复制的特性,还会造成严重的性能损耗。
因此,我们将代码修改如下:
/* Time.h */
#include <iostream>
using std::ostream;
class Time {
// ...
friend ostream &operator<<(ostream &os, const Time &t);
};
ostream &operator<<(ostream &os, const Time &t) {
os << t.hour << ":" << t.minute << ":" << t.second;
return os;
}
/* Time.cpp */
#include "Time.h"
int main() {
Time t(12, 30, 20);
cout << t << " Have a good day, Adehit!";
return 0;
}
至此,我们定义的 Time
类能够正常使用 cout
来进行输出。