【问题标题】:ostream chaining, output orderostream 链接,输出顺序
【发布时间】:2012-02-14 10:02:58
【问题描述】:

我有一个函数,它将ostream 引用作为参数,将一些数据写入流,然后返回对同一流的引用,如下所示:

#include <iostream>

std::ostream& print( std::ostream& os ) {
  os << " How are you?" << std::endl;
  return os;
}

int main() {
  std::cout << "Hello, world!" << print( std::cout ) << std::endl;
}

这段代码的输出是:

 How are you?
Hello, world!0x601288

但是,如果我将链接表达式分成两个语句,像这样

int main() {
  std::cout << "Hello, world!";
  std::cout << print( std::cout ) << std::endl;
}

那么我至少在输出中得到了正确的顺序,但仍然得到一个十六进制值:

Hello, world! How are you?
0x600ec8

我想了解这里发生了什么。普通函数是否优先于operator&lt;&lt;,这就是输出顺序反转的原因?编写将数据插入ostream 但也可以与operator&lt;&lt; 链接的函数的正确方法是什么?

【问题讨论】:

    标签: c++ iostream chaining


    【解决方案1】:

    根据 C++ 标准,您的代码的行为未指定

    说明

    以下内容(为简单起见,我删除了std::endl

    std::cout << "Hello, world!" << print( std::cout ); 
    

    等价于:

    operator<<(operator<<(std::cout, "Hello, World!"), print(std::cout));
    

    这是一个函数调用,传递两个参数:

    • 第一个参数是:operator&lt;&lt;(std::cout, "Hello, World!")
    • 第二个参数是:print(std::cout)

    现在,标准没有指定评估参数的顺序。它是未指定的。但是您的编译器似乎首先评估第二个参数,这就是为什么它首先打印 "How are you?",然后将第二个参数评估为 std::ostream&amp; 类型的值,然后将其传递给调用如上所示(该值是对象std::cout 本身)。

    为什么是十六进制输出?

    您得到十六进制输出,因为第二个参数的计算结果为std::cout,它被打印为十六进制数字,因为std::cout 隐式转换为void* 类型的指针值,这就是它被打印为十六进制数字的原因。

    试试这个:

    void const *pointer = std::cout; //implicitly converts into pointer type!
    std::cout << std::cout << std::endl;
    std::cout << pointer << std::endl;
    

    它将为两者打印相同的值。例如,ideone 的这个例子打印了这个:

    0x804a044
    0x804a044 
    

    还要注意我没有使用 explicit cast;而std::cout隐式转换为指针类型。

    希望对您有所帮助。


    编写将数据插入ostream 但也可以与operator&lt;&lt; 链接的函数的正确方法是什么?

    什么时候取决于你所说的链接是什么意思?显然,以下方法行不通(如上所述):

    std::cout << X << print(std::cout) << Y << Z; //unspecified behaviour!
    

    不管你怎么写print()

    但是这是明确定义的:

    print(std::cout) << X << Y << Z; //well-defined behaviour!
    

    【讨论】:

    • 不回答“编写将数据插入 ostream 但也可以与 operator&lt;&lt; 链接的函数的正确方法是什么?”
    • @Mr.Anubis:我正在吃晚饭:P
    【解决方案2】:

    原因是您的 print() 函数将在语句的其余部分之前进行评估,并返回对 cout 的引用,然后实际打印为指针 (cout

    至于定义一个流感知“函数”,它实际上已经定义了具有相同功能的行为,这是可行的;

    #include <iostream>
    
    template <class charT, class traits>
      std::basic_ostream<charT,traits>& print ( std::basic_ostream<charT,traits>& os )
    {
            os << " How are you?" << std::endl;
            return os;
    }
    
    int main() {
      std::cout << "Hello, world!" << print << std::endl;
    }
    

    另请参阅this answer,了解更多关于“未指定”在这种情况下的实际含义的详细信息。

    【讨论】:

      【解决方案3】:

      十六进制输出

      在 C++11 之前,类std::ostream 具有到void* 的转换功能。由于您的print 函数返回std::ostream&amp;,因此在评估std::cout &lt;&lt; print(...) 时,返回的std::ostream 左值将隐式转换为void*,然后作为指针值输出。这就是为什么会有十六进制输出。

      从C++11开始,这个转换函数is replaced通过一个explicit转换函数到bool,所以试图输出一个std::ostream对象就变得不合时宜了。

      评估顺序

      在C++17之前,重载运算符被认为是分析求值顺序的函数调用,函数调用的不同参数的求值顺序是未指定的。所以print函数先求值也就不足为奇了,导致 How are you?先输出。

      自 C++17 起,运算符&lt;&lt; 的操作数的计算顺序严格从左到右,重载运算符的操作数与内置运算符的计算顺序相同(详见@987654322 @)。所以你的程序总能得到输出(假设print返回可以输出的东西)

      Hello, world! How are you?
      something returned by print
      

      LIVE EXAMPLE

      【讨论】:

        【解决方案4】:

        在您的声明std::cout &lt;&lt; "Hello, world!" &lt;&lt; print( std::cout ) &lt;&lt; std::endl 中,未定义std::cout &lt;&lt; "Hello, world!" 发生在print( std::cout ) 之前还是之后。这就是为什么顺序可能不是您所期望的。

        十六进制值来自于你也在做std::cout &lt;&lt; std::cout 的事实(print 返回std::cout,它被送入&lt;&lt; 链)。右手std::cout 被转换为void * 并打印到输出中。

        【讨论】:

          【解决方案5】:

          这样可以将print&lt;&lt; 结合起来并控制顺序:

          print( std::cout << "Hello, world!" ) << std::endl;
          

          或者,如果您想要使用 &lt;&lt; 调用的函数,请参阅 Joachim 的回答。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-01-13
            • 2011-08-12
            • 2019-04-20
            • 1970-01-01
            • 2012-05-19
            • 2023-03-22
            • 2016-10-19
            • 1970-01-01
            相关资源
            最近更新 更多