目录
C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。
标准库
我们都知道,在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载,使其能够用于不同数据的输入输出,但是输入输出的对象只能是 C++内置的数据类型(例如 bool、int、double 等)和标准库所包含的类类型(例如 string、complex、ofstream、ifstream 等)。
cout << num1 << num2 << endl; // 输出形式
cin >> num1 >> num2 << endl; // 输入形式
重载
对于重载运算符>>,当我们用成员函数写他的时候
void Date::operate<<(ostream &out)
{
out<<year<<"年"<<month<<"月"<<endl;
}
我们会发现程序出现错误,这是因为,成员函数天生自带一个性质
重载为成员函数,this指针默认抢占了第一个形参位置
一个形参位置是左侧运算对象。有用时就变成了 对象<<cout,不符合使用习惯和可读性。
因此我们需要重载为全局函数把ostream/istream找到第一个形参他置就可以了,第二个形参位置当类类型对象。
#include <iostream>
class MyClass {
private:
int data;
public:
MyClass(int d) : data(d) {}
// 声明友元函数
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
};
// 定义友元函数,可以访问 MyClass 的私有成员
std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
os << "MyClass (private data: " << obj.data << ")";
return os;
}
int main() {
MyClass obj(42);
std::cout << "My object: " << obj << std::endl;
return 0;
}
在 MyClass 类中,我们使用
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
将全局函数 operator<< 声明为友元函数。 这样一来,operator<< 函数就可以访问 MyClass 的私有成员变量 data 了。 在 main 函数中,我们可以像使用内置类型一样,直接使用 std::cout << obj 来输出 MyClass 对象。
注意,我们返回的值是
std::ostream&
那么为什么要返回引用值呢?
如果直接返回类值呢
很悲观,我们可能无法做到连续流畅的输出书写
ostream& Date::operate<<(ostream &out,const Date &A)
{
out<<A.year<<"年"<<A.month<<"月"<<endl;
return out;
}//正确
ostream Date::operate<<(ostream &out,const Date &A)
{
out<<A.year<<"年"<<A.month<<"月"<<endl;
return out;
}//错误
让我们深入分析一下,cin >> c1 做了什么?
cin 是一个全局的 istream 对象,代表标准输入流(通常是键盘输入)。
>> 运算符被重载,用于从输入流中提取数据。
cin >> c1 的意思是从 cin (标准输入) 读取数据,然后将数据转换为 complex 类型,最后赋值给 c1。
为什么可以链式操作? cin >> c1 执行完读取和赋值操作后,会返回 cin 对象本身的引用。 这意味着 cin >> c1 >> c2 等价于 (cin >> c1) >> c2。 由于 (cin >> c1) 返回的是 cin 的引用,所以可以继续使用 >> 从 cin 中读取数据赋值给 c2。
为什么 cin
的副本不能读取 c2
呢?
这涉及到 C++ 输入流的内部工作机制。
1. 流指针: cin 对象内部维护了一个指向输入缓冲区的指针,称为流指针。 这个指针指示了下次读取操作应该从缓冲区的哪个位置开始。
2. 读取操作的影响: 当你执行 cin >> c1 时,cin 对象会: 从流指针当前位置开始读取数据,直到遇到空格或换行符。 将读取到的数据转换为 complex 类型,并赋值给 c1。 移动流指针,使其指向读取数据的下一个位置。
3. 副本的问题: 当你创建 cin 的副本时,副本也会复制一份流指针。 但是,这个副本的流指针和原始 cin 对象的流指针是相互独立的。 当你对副本执行 >> c2 操作时,它会: 从副本的流指针当前位置开始读取数据。 由于副本的流指针并没有随着原始 cin 对象的读取操作而移动,所以它仍然指向上一次读取操作结束的位置。 结果就是,c2 会读取到与 c1 相同的数据,或者读取到一些不完整的数据。
cin
的副本拥有独立的流指针,无法跟踪原始 cin
对象的读取操作。因此,副本无法正确读取后续的数据。这就是为什么 cin >> c1
需要返回引用,以确保链式操作作用于同一个 cin
对象,保证流指针的一致性。
看看例子
#include <iostream>
#include <complex>
using namespace std;
istream& operator>>(istream& is, complex<double>& c) {
double real, imag;
char sign, i;
is >> real >> sign >> imag >> i; // 假设输入格式是 "1+2i"
c = complex<double>(real, sign == '+' ? imag : -imag);
return is;
}
int main() {
complex<double> c1, c2;
auto cin_copy = cin; // 创建 cin 的副本
cin_copy >> c1; // 使用副本读取 c1
cin >> c2; // 使用原始 cin 读取 c2
cout << "c1: " << c1 << endl;
cout << "c2: " << c2 << endl;
return 0;
}
创建副本: auto cin_copy = cin;
创建了 cin 的副本 cin_copy,此时 cin 和 cin_copy 的流指针都指向输入缓冲区的起始位置,也就是 "1"。
cin_copy >> c1: cin_copy 从流指针当前位置 ("1") 开始读取,直到遇到空格,成功读取 1+2i 并赋值给 c1。
cin_copy 的流指针移动到空格字符 " " 的位置。 cin >> c2: cin 从它自己的流指针当前位置开始读取。
由于 cin 的流指针并没有被移动过,所以它仍然指向输入缓冲区的起始位置 "1"。 cin 会再次读取 "1+2i" 并尝试赋值给 c2,这显然是不符合预期的。
因此,我们需要传引用返回值,不要忘记
ostream& Date::operate<<(ostream &out,const Date &A)
{
out<<A.year<<"年"<<A.month<<"月"<<endl;
return out;
}
很简单的cin cout之中也藏着很多的学问,做一个程序员就是在不断地学习的过程之中,路漫漫其修远兮,吾将上下而求索,诸君,加油吧!!