Daniel_yuan's Blog

芙卡米天下第一可爱 n(*≧▽≦*)n

0%

快读相关

琢磨了一下午,在输入输出流上面有了一点点自己的理解。


关于重载运算符读入结构体。

对于一个结构体,如果要读入它我们是不能直接用 cin 的,但是我们可以重载输入流的运算符 >>,从而使得它可以支持这个操作。

输入流 cin 的数据类型是 istream。我们不妨把 >> 看成是一个二元运算符,其左操作数是一个 istream 的变量,右操作数是一个需要读入的东西。

不难发现,我们有时候会连写 cin,如 cin >> a >> b,注意到 >> 是左结合的,所以可以看成是 (cin >> a) >> b。可以发现后面的操作符少了一个左操作数,所以 cin >> a 的返回值应该还是 cin 这个输入流变量。

这样我们就有了一个大致的想法,重载运算符 >>,左操作数读入 istream,右操作数读入 structName,读入完之后返回 istream。即:

1
2
3
4
istream& operator >> (istream &is, structName &a) {
/* Input a */
return is;
}

如果把这个重载写在全局是没有问题的,但是如果写在结构体内就会 CE。

这是结构体的特性导致的,在重载二元运算符的时候,其第一个参数会默认为它自己(比如在重载矩阵乘法的时候),所以需要加上 friend 标识使得它合法。

即如果把重载写在全局,那么就不需要加 friend 标识,否则必须在重载定义前加。即:

1
2
3
4
friend istream& operator >> (istream &is, structName &a) {
/* Input a */
return is;
}

类似的,输出也是这么重载。输出流的类型是 ostream,标识是 cout。其它规则与输入一样。下面给出一个例子:

1
2
3
4
friend ostream& operator << (ostream &os, structName a) {
/* Output a */
return os;
}

关于重载运算符的快读。

快读用的时候不太爽,因为如果要读很多个数就要一个一个 read。考虑用上面的重载运算符优化。

但是如果直接重载 >>,<<,而且用输入输出流的 istream,ostream 的话,这个快读是无效的,因为 c++ 内部的读入就是通过重载实现。

所以考虑自己定义一个读入结构体 Input。重定义左操作数为结构体 Input 右操作数为整形的 >>,然后在运算内写快读,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Input {
private :
char buf[1000000], *p1 = buf, *p2 = buf;
public :
inline char gc() {
if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
return p1 == p2 ? EOF : *(p1++);
}
template <typename T> inline Input& operator >> (T &n) {
n = 0; RI ch = gc(), f;
while ((ch < '0' || ch > '9') && ch != '-') ch = gc();
f = (ch == '-' ? ch = gc(), -1 : 1);
while (ch >= '0' && ch <= '9') n = n * 10 + (ch ^ 48), ch = gc();
n *= f;
return *this;
}
} fin;

在使用的时候,fincin 是类似的用法。如果需要增加功能,如输入字符串等,就直接新定义 >> 即可。因为根据 c++ template 的性质,函数匹配会优先匹配实参,也就是说如果在这个结构体内还有 inline Input& operator >> (char *s) {},然后在输入的时候右操作数是 char* 的话,会优先匹配这个函数。

快输同理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Output {
private :
char ouf[1000000], *P1 = ouf, *P2 = ouf;
char Of[105], *O1 = Of, *O2 = Of;
public :
inline void Flush() { fwrite(ouf, 1, P2 - P1, stdout); P2 = P1; }
inline void pc(char ch) {
*(P2++) = ch;
if (P2 == P1 + 1000000) Flush();
}
template <typename T> inline Output& operator << (T n) {
if (n < 0) pc('-'), n = -n;
if (n == 0) pc('0');
while (n) *(O1++) = (n % 10) ^ 48, n /= 10;
while (O1 != O2) pc(*(--O1));
return *this;
}
inline Output& operator << (char ch) { pc(ch); return *this; }
~Output() { Flush(); }
} fout;

因为我们这里使用的是 fwrite,所以在程序结束的时候可能在输出数组中仍然有未输出的字符,这时候就需要 Flush。但是为了避免在程序结束后忘记 Flush,我们给 Output 类型的结构体一个析构函数,在程序结束析构它的时候进行 Flush

还有一点就是不管是快读还是快输,函数的返回值一定要是 Input/Output &,即传引用。不然在 return *this 的时候会把这个结构体复制一份返回,这不是我们期望看到的。

但是这样还是有点奇怪,因为 finfout 我们不习惯写,我们可以 #define cin fin#define cout fout。而且可以 define endl 等。