23

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

ostream& tab(ostream & output)
{
    return output<< '\t';
} 

这可以像这样在main()中使用:

cout<<'a'<<tab<<'b'<<'c'<<endl;

请解释一下这一切是如何工作的?如果operator<<假定第二个参数是指向接受并返回ostream &的函数的指针,那么请解释我为什么需要它?如果函数不接受并返回ostream &但它是void而不是ostream &会有什么问题?

同样有趣的是,为什么“dec”、“hex”操纵器会在我不改变它们之前生效,但是应该始终使用用户定义的操纵器才能对每个流式传输生效?

4

3 回答 3

27

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

basic_ostream<charT,traits>& operator<<(
    basic_ostream<charT,traits>& (*pf) (basic_ostream<charT,traits>&) );

影响:无。不作为格式化输出函数(如 27.6.2.5.1 中所述)。

回报:pf(*this)

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

这意味着您可以将具有此签名的函数“流式传输”到ostream对象,并且它具有在流上调用该函数的效果。如果您在表达式中使用函数的名称,那么它(通常)会转换为指向该函数的指针。

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

   ios_base& hex(ios_base& str);

效果:呼叫str.setf(ios_base::hex, ios_base::basefield)

返回:str。

这意味着流式传输hex到 anostream会将输出基本格式标志设置为以十六进制输出数字。操纵器本身不输出任何内容。

于 2011-01-08T12:43:02.430 回答
11

除了没有为它定义重载的 << 运算符外,它没有任何问题。<< 的现有重载需要一个带有签名ostream& (*fp)(ostream&)的操纵器。

如果你给它一个类型为ostream& (*fp)()的操纵器,你会得到一个编译器错误,因为它没有operator<<(ostream&, ostream& (*fp)())的定义。如果你想要这个功能,你必须重载 << 操作符来接受这种类型的操纵器。

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

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

既然您知道如何创建您描述的功能,这就是我们不这样做的原因:

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

想想这样的流

cout << "something here" << tab << "something else"<< endl;

真正意思

(((cout << "something here") << tab ) << "something else" ) << endl);

其中每组括号对 cout 执行某些操作(写入、修改等),然后返回 cout 以便下一组括号可以对其进行处理。

如果您的制表符修饰符/函数没有引用 ostream,则必须以某种方式猜测 << 运算符左侧的 ostream 是什么才能执行其任务。您是否正在使用 cour、cerr、某些文件流……?除非以某种方式将信息传递给函数的内部,否则它们永远不会知道,为什么不知道如何像引用它一样简单。

现在要真正把重点放在家里,让我们看看endl到底是什么以及我们使用的 << 运算符的重载版本:

该运算符如下所示:

  ostream& ostream::operator<<(ostream& (*m)(ostream&)) 
  {  
      return (*m)(*this);
  }

endl 看起来像这样:

  ostream& endl(ostream& os)      
  {  
      os << '\n'; 
      os.flush();     
      return os;
  }

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

我希望这能解决问题

PS - 如果你不小心发现了我的车,请把它洗干净。

于 2011-06-10T22:24:20.713 回答
4

通常流操纵器会在流对象上设置一些标志(或其他设置),以便下次使用时,它会根据标志进行操作。因此,操纵器返回其传递的相同对象。当然,调用操纵器的operator<<重载已经有了这个对象,所以正如您所注意到的,在这种情况下并不严格需要返回值。我认为这涵盖了所有标准操纵器——它们都返回输入。

然而,使用返回值,框架足够灵活,自定义流操纵器可以返回不同的对象,大概是给定对象的包装器。然后,这个其他对象将从 中返回cout << 'a' << tab,并且可以执行内置ostream格式设置不支持的操作。

不过,不确定如何安排释放另一个对象,所以我不知道这有多实用。它可能必须是一些特殊的东西,例如由ostream自身管理的代理对象。然后操纵器将仅适用于积极支持它的自定义流类,这通常不是操纵器的重点。

于 2011-01-08T12:40:58.320 回答