我有一个函数,它将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<<
,这就是输出顺序反转的原因吗?编写将数据插入ostream
但也可以与operator<<
链接的函数的正确方法是什么?
答案 0 :(得分:5)
根据C ++标准,代码的行为是未指定的。
以下内容(为简单起见,我删除了std::endl
)
std::cout << "Hello, world!" << print( std::cout );
相当于:
operator<<(operator<<(std::cout, "Hello, World!"), print(std::cout));
这是一个函数调用,传递两个参数:
operator<<(std::cout, "Hello, World!")
print(std::cout)
现在,标准没有指定评估参数的顺序。它是未指定的。但是你的编译器似乎首先评估第二个参数,这就是为什么它打印“你好吗?”首先,将第二个参数计算为std::ostream&
类型的值,然后传递给它上面显示的调用(该值是对象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
另请注意,我没有使用显式强制转换;相反std::cout
隐式转换为指针类型。
希望有所帮助。
编写将数据插入
ostream
但又可以与operator<<
链接的函数的正确方法是什么?
什么时候取决于链接的意思?显然,以下内容不起作用(如上所述):
std::cout << X << print(std::cout) << Y << Z; //unspecified behaviour!
无论你如何写print()
。
然而,这是明确界定的:
print(std::cout) << X << Y << Z; //well-defined behaviour!
答案 1 :(得分:3)
原因是你的print()函数将在语句的其余部分之前被评估,并返回对cout的引用,然后cout实际打印为指针(cout&lt;&lt; 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,了解更多有关“未指明”在这种情况下实际含义的细节。
答案 2 :(得分:1)
在您的陈述std::cout << "Hello, world!" << print( std::cout ) << std::endl
中,std::cout << "Hello, world!"
之前或之后是否print( std::cout )
未定义。这就是为什么订单可能不符合您的预期。
十六进制值来自于您正在执行std::cout << std::cout
(print
返回std::cout
并将其输入<<
链)。右手std::cout
将转换为void *
并打印到输出中。
答案 3 :(得分:1)
这可以合并print
与<<
并控制订单:
print( std::cout << "Hello, world!" ) << std::endl;
或者,如果你想要一个用<<
调用的函数,请参阅Joachim的答案。
答案 4 :(得分:1)
在C ++ 11之前,类std::ostream
具有转换函数void*
。由于您的print
函数返回std::ostream&
,因此在评估std::cout << print(...)
时,返回的std::ostream
左值将隐式转换为void*
,然后作为指针值输出。这就是十六进制输出的原因。
从C ++ 11开始,这个转换函数is replaced由显式转换函数转移到bool
,因此尝试输出std::ostream
对象变得不合适形成。
在C ++ 17之前,重载运算符被认为是用于分析评估顺序的函数调用,并且未指定函数调用的不同参数的评估顺序。因此,首先评估print
函数并不奇怪,这导致首先输出How are you?
。
从C ++ 17开始,运算符<<
的操作数的评估顺序严格地从左到右,并且重载运算符的操作数与bulit-in的操作数共享相同的评估顺序(查看更多详细信息) here)。因此,您的程序将始终获得输出(假设print
返回能够输出的内容)
Hello, world! How are you?
something returned by print