std::endl是签名的函数模板:
template<class CharT, class Traits>
std::basic_ostream<CharT,Traits>& endl(std::basic_ostream<CharT,Traits>&);
std::basic_ostream::operator<< 重载 std::basic_ostream<CharT,Traits>>::operator<<(std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&)) 接受某个签名的函数。
当您执行std::cout << std::endl 时,会在std::endl 上完成重载解析,这会确定std::endl 的正确模板类型并实例化一个函数。然后它衰减成一个指针,并传递给operator<<。
std::basic_ostream::operator<< 然后调用相关ostream 上的函数,并返回返回值。比如:
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>&
std::basic_ostream<CharT, Traits>::operator<<(
std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&)
) {
return func(*this);
}
但具体实现取决于编译器库编写者1。
std::endl 会打印一个换行符,然后告诉 ostream 刷新自己。你可以通过这两行代码模拟std::cout << std::endl;:
std::cout.put(std::cout.widen('\n'));
std::cout.flush();
std::endl 的具体实现方式取决于编译器,但以上是您可能如何编写它的一个不错的近似值(自然是在通用流上)。
如果您#include <ostream>,则保证您可以访问std::endl。如果您包含 std 库中的任何其他头文件,您可能可以访问它。究竟是什么文件定义它又取决于实现。
std::endl 被称为“io 操纵器”。该技术旨在允许通过将<< 调用链接在一起,将操纵 io 流状态的函数设置为与输出命令“内联”。
要创建您自己的,如果您希望它与单一类型的 ostream 一起工作,只需创建一个函数,该函数通过引用获取那种 ostream,并通过引用返回它。它现在是一个 io 操纵器。
如果要处理一组流,请创建如下模板:
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>& bob(std::basic_ostream<CharT, Traits>& os)
{
return os << os.widen('b') << os.widen('o') << os.widen('b');
}
现在是一个打印"bob"的io操纵器。它可以对有问题的basic_ostream 做任何你想做的事情。
另一种计划是这样的:
struct bob_t {
template<class OS>
OS& operator()(OS& os)const {
return os << os.widen('b') << os.widen('o') << os.widen('b');
}
template<class OS>
operator OS&(*)(OS&)() const {
return [](OS& os)->OS&{ return bob_t{}(os); };
}
};
static const bob_t bob;
bob 现在是一个可以用作 io 操纵器的对象。
1 这个<< 重载是A->(A->A)->A 类型的函数。基本上,我们不是将 X 传递给 f,而是将 X 和 f 传递给 <<,然后 f(X)。纯语法糖。
std::endl 是一个模板这一事实意味着,由于这种技术,完美转发它有点痛苦。我最终定义了无状态函数 endl_t 类型,并带有 operator basic_ostream<CharT,Traits>&(*)(basic_ostream<CharT,Traits>&)()const 重载,因此有时我可以通过完美的转发代理传递重载集。
然后我们可以将f:(A->A) 的整个重载集传递给<<,并让“下一层”解决重载。