琢磨了一下午,在输入输出流上面有了一点点自己的理解。
关于重载运算符读入结构体。
对于一个结构体,如果要读入它我们是不能直接用 cin 的,但是我们可以重载输入流的运算符 >>,从而使得它可以支持这个操作。
输入流 cin 的数据类型是 istream。我们不妨把 >> 看成是一个二元运算符,其左操作数是一个 istream 的变量,右操作数是一个需要读入的东西。
不难发现,我们有时候会连写 cin,如 cin >> a >> b,注意到 >> 是左结合的,所以可以看成是 (cin >> a) >> b。可以发现后面的操作符少了一个左操作数,所以 cin >> a 的返回值应该还是 cin 这个输入流变量。
这样我们就有了一个大致的想法,重载运算符 >>,左操作数读入 istream,右操作数读入 structName,读入完之后返回 istream。即:
1 | istream& operator >> (istream &is, structName &a) { |
如果把这个重载写在全局是没有问题的,但是如果写在结构体内就会 CE。
这是结构体的特性导致的,在重载二元运算符的时候,其第一个参数会默认为它自己(比如在重载矩阵乘法的时候),所以需要加上 friend 标识使得它合法。
即如果把重载写在全局,那么就不需要加 friend 标识,否则必须在重载定义前加。即:
1 | friend istream& operator >> (istream &is, structName &a) { |
类似的,输出也是这么重载。输出流的类型是 ostream,标识是 cout。其它规则与输入一样。下面给出一个例子:
1 | friend ostream& operator << (ostream &os, structName a) { |
关于重载运算符的快读。
快读用的时候不太爽,因为如果要读很多个数就要一个一个 read。考虑用上面的重载运算符优化。
但是如果直接重载 >>,<<,而且用输入输出流的 istream,ostream 的话,这个快读是无效的,因为 c++ 内部的读入就是通过重载实现。
所以考虑自己定义一个读入结构体 Input。重定义左操作数为结构体 Input 右操作数为整形的 >>,然后在运算内写快读,即:
1 | class Input { |
在使用的时候,fin 和 cin 是类似的用法。如果需要增加功能,如输入字符串等,就直接新定义 >> 即可。因为根据 c++ template 的性质,函数匹配会优先匹配实参,也就是说如果在这个结构体内还有 inline Input& operator >> (char *s) {},然后在输入的时候右操作数是 char* 的话,会优先匹配这个函数。
快输同理:
1 | class Output { |
因为我们这里使用的是 fwrite,所以在程序结束的时候可能在输出数组中仍然有未输出的字符,这时候就需要 Flush。但是为了避免在程序结束后忘记 Flush,我们给 Output 类型的结构体一个析构函数,在程序结束析构它的时候进行 Flush。
还有一点就是不管是快读还是快输,函数的返回值一定要是 Input/Output &,即传引用。不然在 return *this 的时候会把这个结构体复制一份返回,这不是我们期望看到的。
但是这样还是有点奇怪,因为 fin 和 fout 我们不习惯写,我们可以 #define cin fin,#define cout fout。而且可以 define endl 等。