第八十一讲 再探OI


ok,最近都比较忙,所以没时间打理,好吧,今天抽空来继续探索IO,上一讲我们说过来今天的内容会比较深奥难懂一些,当然大家也不要担心的了,多看几遍还是能够看得懂的。

好,继续我们的主题,我们来看看下面的代码:
————————–
int num;


cout<<"

Pls enter a num:"

;


cin>>num;


cout<<endl;


—————————

这几句代码不难理解,而且是最常见的了,但是大家可能一直忽略了一个问题,cout是带有缓冲区的输出流,什么意思呢?简单点说就是我们要输出的文件会先被写入缓冲区中,当缓冲区满了之后才会被写出,也就是说只有当我们要输出的内容占满cout的缓冲区时他们才会出现在我们的面前,至于这个缓冲区是什么样的,他有多大我并不知道,毕竟我也没去研究过iostream的源码(不同的编译器实现方案不一样),但是这部妨碍我们对IO的探究。

根据上面我们所知道的理论,如果说当"

Pls enter a num:"

没有将缓冲区占满,那么我们可能就收不到这句提示了,这是不是意见很糟糕的事,也许我们可能会一直想在等待,等待这句提示然后我们才会想起输入一个整数。如果这还不够糟糕,那么什么才算是糟糕?不过好在iostream并不是没有给我解决方案,iostream里面的flush就是强行刷新缓冲区的,所以我们可以这样用:
——————————–
cout<<"

Pls enter a num:"

<<
flush;


———————————
ok,总算进入正题了,我们就从flush开始,假设我们的flush像下面这样,我们暂时抛开我们对flush的理解,我们来看看一个新鲜的:
———————————–
ostream&

flush(ostream&

file);


———————————–
假设这个flush的存在就是为了清除缓冲区,那么我们可以这样来使用他了:
——————————-
cout<<"

Pls enter a num:"

;


flush(cout);


——————————

当如,如果我们想要写得更加紧凑一些,我们可以这样写:
——————————–
flush(cout<<"

Pls enter a num:"

);


——————————–
当然这没关系的了,但是如果我们多输出几个呢????
———————————
cout<<1;


flush(cout);


cout<<2;


flush(cout);


cout<<3;


flush(cout);


———————————

如果我们再将上面的代码紧凑一些呢?
———————————–
flush(flush(flush(cout<<1)<<2)<<3);


———————————–

看上去有些复杂难解,是的,我第一次看到这玩意的时候我也觉得难以理解,这才三个如果再多一些那不是要让人眼花缭乱了吗?ok,我们可以换种方法来解决这个问题,假设我们有这么一个空类,然后让这个空类重载operator<<操作符:
———————————–
class FLUSH{
};


ostream&

operator<<(ostream&

os,FLUSH f){

return flush(os);


}
FLUSH _flush;


———————————

这个空类啥都不做,这不是废话吗?都说了空类当然是啥都不做的了,他的存在就是为了简化刚才我们上面那种操蛋的写法,所以现在我们已经给他定义了一个全局对象_flush,那么我们可以简单把刚才那种操蛋的写法简化成如下这样:
————————————–
cout<<1<<_flush<<2<<_flush<<3<<_flush;


————————————–

这个方法说实在的没啥好谈的,简单点说就是将flush(cout),所以,当我们写出cout<<_flush的时候他调用的是ostream&

operator<<(cout,_flush),然后这个操作符封装了我们真正的flush(cout),所以他能够很好的工作,但是真的没啥吸引力,因为他太过极限,说到吸极限,我们来看看另一种方法,这一次我们不用空类来表示:
—————————————
ostream&

operator<<(ostream&

os,ostream&

(*func)(ostream)){

return (*func)(os);


}
—————————————

这里我们使用了函数指针的东西,*func不代表某个具体的函数,他代表着一类的函数——以ostream为参数,返回ostream的引用的函数,有了上面这东西,我们再不用使用那些无聊的伪对象了,现在我们可以直接这样用:
—————————————
cout<<1<<
flush<<2<<
flush<<3<<
flush;


—————————————

上面操作能够成功的奥妙就是这里:
——————————
cout<<_f == _f(cout)
——————————-

如果不明白,我再简单的说一下上面的调用机制:当cout<<_f时,(如果_f满足我们所说的*func)这里其实调用就是ostream&

operator<<(cout,_f);

现在该是明白了吧。

我们再把情况更加复杂化一些,加入我们有一个函数,将一个long对象转换为二进制表示:
————————————
bitset<10>get(long i){

bitset<10>b(i);

//这是个有问题的设计,但是不影响我们这里的使用,如果大家有兴趣探索,可以先去看看bitset这个class。

return b;


}
————————————-
如果我们现在想要这样使用:
————————————
cout<<get(150)<<"

"

<<get(180);


————————————
现在摆在我们面前的问题是怎么阻止我们先对150求值然后保存结果后再对180求值保存结果然后再调用这几个operator<<呢?因为如果这样做的话,打印出来的结果将是错误的,为什么呢?因为连续两次调用get返回的指针指向同一块内存,所以无论是先对那一个求值,结果都会一个覆盖一个,当然或许我们可以考虑使用环形缓冲区结构来存放结果,但是这样能够解决暂时的问题,当连续多次调用后这个环形缓冲区会重新回到起点,还是会被覆盖。

上面我们用函数指针来解决flush的问题,现在我们用函数对象来解决这个问题:
—————————————-
class obj{
public:

typedef ostream&

(*_Func)(ostream&

,long);



obj(ostream&

(*func)(ostream&

,long),long v):

_func(func),_val(v){ }

ostream&

operator()(ostream&

os) const{

return (*_func)(os,_val);


}
private:

_Func _func;



long _val;


};


——————————————
现在,我们再来重载一下operator<<:
——————————————
ostream&

operator<<(ostream&

os,const obj&

_ob){

return _ob(os);


}
——————————————

最后我们再来看看关键部分:
—————————————–
ostream&

binaryconve(ostream&

os,long n){

return os<<get(n);


}
—————————————–
这里是安全的,因为我们一旦获得get(n)就马上使用,所以不会造成缓冲区被覆盖的问题。最后我们再重载binaryconve来生成一个obj对象:
—————————————–
obj binaryconve(long n){

return obj(binaryconve,n);


}
——————————————

ok,到这里,我们可以放心的这样用了:
————————————–
cout<<get(150)<<"

"

<<get(180);


————————————–

现在我们来总结一下:

当我们输出的是无参数的情况时:

cout<<
f
可以转换为f(cout),而如果我们输出多参数的时候:

cout<<
f(a0,a1,a2….an)
可以转换为f(cout,a0,a1,a2…an).

这一讲的内容有些无法理解,可能需要多看几遍多思考一阵才会明白是怎么回事,当然了,这是一些很底层的实现方案,标准库里面的很多实现就是根据这一思想得来的。

第八十一讲 再探OI

============================
回复D直接查看目录

原文始发于微信公众号(

C/C++的编程教室

):第八十一讲 再探OI

|

发表评论