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).
这一讲的内容有些无法理解,可能需要多看几遍多思考一阵才会明白是怎么回事,当然了,这是一些很底层的实现方案,标准库里面的很多实现就是根据这一思想得来的。
============================
回复D直接查看目录
原文始发于微信公众号(
C/C++的编程教室
):第八十一讲 再探OI
|