流操纵器如何工作?

Nar*_*rek 20 c++ iostream ostream

众所周知,用户可以像这样定义流操纵器:

ostream& tab(ostream & output)
{
    return output<< '\t';
} 
Run Code Online (Sandbox Code Playgroud)

这可以在main()中使用,如下所示:

cout<<'a'<<tab<<'b'<<'c'<<endl;
Run Code Online (Sandbox Code Playgroud)

请解释一下这一切是如何工作的?如果operator <<假定为第二个参数指向获取并返回ostream的函数的指针,那么请解释我为什么有必要?如果函数不接受并返回ostream并且它是无效而不是ostream&那么会出错?

另外有趣的是为什么"十进制","十六进制"操纵器生效,直到我不在它们之间进行更改,但应始终使用用户定义的操纵器以使每个流式传输生效?

CB *_*ley 22

该标准operator<<basic_ostream类模板中定义了以下重载:

basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& (*pf) (basic_ostream<charT,traits>&) );
Run Code Online (Sandbox Code Playgroud)

效果:无.不像格式化输出函数(如27.6.2.5.1中所述).

返回:pf(*this).

该参数是一个指向函数的指针,该函数获取并返回对a的引用std::ostream.

这意味着您可以将具有此签名的函数"流"化为ostream对象,并且它具有在流上调用该函数的效果.如果在表达式中使用函数的名称,则它(通常)转换为指向该函数的指针.

std::hex是一个std::ios_base定义如下的操纵器.

   ios_base& hex(ios_base& str);
Run Code Online (Sandbox Code Playgroud)

效果:通话str.setf(ios_base::hex, ios_base::basefield).

返回:str.

这意味着,流hex至一个ostream将设置输出基底格式化标志来输出数字以十六进制.操纵器本身不输出任何东西.

  • 我认为问题是,“为什么操纵器本身没有定义为返回`void`,而操纵器的`operator&lt;&lt;`重载定义为调用`pf(*this)`然后返回`*this`”? (2认同)
  • @Steve Jessop:我认为问题是:“请解释一下这一切是如何工作的?”。它之所以有效,是因为“basic_ostream”定义了特定的重载,这就是需要给定签名的原因。不过,我没有遵循最后一段中的问题。 (2认同)

Bob*_*630 8

除了没有为它定义的重载<<运算符之外,它没有任何问题.<<的现有重载是期望具有签名ostream&(*fp)(ostream&)的操纵器.

如果你把它用型机械手的ostream&(*FP)() ,你会因为它得到一个编译错误具有定义运算符<<(ostream的&,ostream的&(*FP)()) .如果你想要这个功能,你必须重载<<运算符来接受这种类型的操纵器.

你必须为此写一个定义:
ostream&ostream :: operator <<(ostream&(*m)())

请记住,这里没有任何神奇的事情发生.流库严重依赖于标准 C++特性:运算符重载,类和引用.

既然您知道如何创建您描述的功能,那么我们就不这样做了:

如果没有传递对我们试图操作的流的引用,我们就无法修改连接到最终设备的流(cin,out,err,fstream等).函数(修饰符都只是具有花哨名称的函数)要么必须返回一个与<<运算符左边的那个没有任何关系的新ostream,要么通过一些非常丑陋的机制,找出它应该是哪个ostream连接修改器右边的所有内容都不会使它到达最终设备,但宁愿被发送到函数/修改器返回的任何ostream.

想想这样的流

cout << "something here" << tab << "something else"<< endl;
Run Code Online (Sandbox Code Playgroud)

真正意思

(((cout << "something here") << tab ) << "something else" ) << endl);
Run Code Online (Sandbox Code Playgroud)

其中每组括号都执行cout(写入,修改等),然后返回cout,以便下一组括号可以对其进行处理.

如果你的制表符修饰符/函数没有引用ostream,它必须以某种方式猜测<<操作符左边的ostream是什么来执行它的任务.你在使用cour,cerr,一些文件流......?函数的内部结构将永远不会知道,除非他们将这些信息传递给某些信息,为什么不如此简单,如何引用它.

现在要真正推动这一点,让我们看看endl到底是什么以及我们正在使用的<<运算符的哪个重载版本:

此运算符如下所示:

  ostream& ostream::operator<<(ostream& (*m)(ostream&)) 
  {  
      return (*m)(*this);
  }
Run Code Online (Sandbox Code Playgroud)

endl看起来像这样:

  ostream& endl(ostream& os)      
  {  
      os << '\n'; 
      os.flush();     
      return os;
  }
Run Code Online (Sandbox Code Playgroud)

endl的目的是添加换行符并刷新流,确保流的内部缓冲区的所有内容都已写入设备.为此,首先需要为此流写一个'\n'.然后它需要告诉流冲洗.endl知道要写入和刷新哪个流的唯一方法是让操作员在调用它时将该信息传递给endl函数.就像我告诉你洗车一样,但从来没有告诉你在整个停车场里哪辆车是我的.你永远无法完成你的工作.你需要我把你的车交给我,或者我可以自己洗车.

我希望能够解决问题

PS - 如果你偶然发现我的车,请洗一下.