在之前几篇,如构造函数与析构函数拷贝构造函数拷贝赋值运算符核心成员函数后,为了让该类能够像内置类型一样方便地打印到屏幕上,需要为其重载输出操作符 <<。

这一需求与之前复数类的操作符重载思路一致,但需要注意语法上的特殊要求。

全局函数与成员函数的选择

image

为什么成员函数会导致方向颠倒?这源于 this 指针的隐含机制。

成员函数 operator<< 第一个隐含参数是 this,指向左侧的 String 对象,第二个参数才是 ostream&

全局函数没有 this 指针,两个参数顺序完全由开发者自己控制,保持 cout << str 的自然写法。

简言之,输出操作符必须为全局函数,才能让标准输出流 cout 出现在左侧,符合所有程序员的阅读习惯。

operator<< 的重载签名

标准签名模版:

1
ostream& operator<<(ostream& os, const String& str);

第一个参数是输出流引用(左侧操作数),第二个参数是常量字符串引用(右侧操作数)。返回值类型为 ostream&,支持链式调用(如 cout << str1 << str2)。

签名的每个部分都有特定设计意图,理解它们有助于避免常见错误。

如参数1:ostream& os —— 必须是引用,因为流对象不可拷贝。

参数2:const String& str —— 常量引用,避免拷贝且保证不修改原对象。

返回值:ostream& —— 返回 os 本身,实现链式调用。

借助 c_str() 实现输出

实现思路:在 operator<< 函数内部,调用字符串对象的 c_str() 成员函数获取底层的 C 风格字符串指针(const char*),然后将其传递给 os << 即可。因为标准库已经为 const char* 重载了输出操作符。

实现代码:

1
2
3
4
ostream& operator<<(ostream& os, const String& str) {
os << str.c_str();
return os;
}

这是最简洁、最安全的实现方式,完全复用标准库对 const char* 的输出能力。

c_str() 返回的是字符串内部动态数组的首地址,其生命周期与 String 对象绑定。理解这一点有助于避免悬挂指针。

返回的指针指向堆内存,由 String 类的析构函数负责释放。

调用 c_str() 后,若 String 对象被销毁,返回的指针变为悬挂指针。

因此,operator<< 中直接使用 c_str() 是安全的,因为输出操作是同步的,在函数返回前 String 对象一直存活。