endl 实际上是什么?当然,它会打印一个新行并刷新 ostream 缓冲区。但实际上它是什么?
std::endl 是一个函数,它将std::ostream& 引用作为输入并返回std::ostream& 引用作为输出:
template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );
std::basic_ostream::operator<< 具有接受指向此类函数的指针的重载:
template<
class CharT,
class Traits = std::char_traits<CharT>
> class basic_ostream : virtual public std::basic_ios<CharT,Traits>
{
...
basic_ostream& operator<<(
std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&)
);
...
};
这个重载将调用传递的函数,给它一个std::ostream 正在调用操作符的对象,例如:
template<class CharT, class Traits>
basic_ostream<CharT,Traits>& basic_ostream<CharT,Traits>::operator<<(
std::basic_ostream<CharT,Traits>& (*func)(std::basic_ostream<CharT,Traits>&) )
{
func(*this);
return *this;
}
std::endl 的实现然后可以写入给定的std::ostream 并刷新它,例如:
template<class CharT, class Traits>
std::basic_ostream<CharT,Traits>& endl( std::basic_ostream<CharT,Traits>& os )
{
os.put(os.widen('\n'));
os.flush();
return os;
}
所以,当你有这样的陈述时:
std::cout << std::endl
它实际上会在内部调用它:
std::cout.operator<<(&std::endl)
然后调用:
std::endl(std::cout)
程序员可以定义像endl这样的“实体”吗?
是的。任何与上述签名(std::basic_ostream<CharT,Traits>& (*)(std::basic_ostream<CharT,Traits>&))匹配的函数都可以传递给operator<<。
执行命令时实际发生了什么,例如:cout << setprecision(5);
setprecision() 看起来像一个函数调用
它是一个函数调用。接受用户输入的 I/O 操纵器与不接受任何用户输入的 I/O 操纵器的工作方式略有不同。
为了将用户输入应用到std::ostream 对象(或std::istream 对象),这样的操纵器返回一个实现定义类型的实例,该实例保存输入,并且然后重载非成员 operator<< 以采用该类型。当调用该重载时,它可以根据需要将输入应用于std::ostream(或std::istream)。
在std::setprecision() 的情况下,它接受int 作为输入,并返回一个包含int 的实现定义类型,然后该类型将int 传递给std::ostream::precision(),例如:
struct PrecisionType { int value; };
PrecisionType setprecision( int n )
{
return PrecisionType{ n };
}
template<class CharT, class Traits>
std::basic_ostream<CharT,Traits>& operator<<( std::basic_ostream<CharT,Traits>& os, const PrecisionType &input )
{
os.precision(input.value);
return os;
}
因此,这样的声明:
std::cout << std::setprecision(5)
实际上会这样称呼:
PrecisionType temp = std::setprecision(5);
operator<<(std::cout, temp)
然后内部调用:
std::cout.precision(temp.value)
但在使用 cout 实例时没有打印任何内容。它改变了精度
正确,因为set::setprecision() 返回的操纵器不会向std::ostream 的输出缓冲区写入任何内容,它只是调整std::ostream 本身。
但是,如果 I/O 操纵器愿意,没有什么可以阻止它写入 std::ostream(或从 std::istream 读取)。
为什么干脆不使用相应的函数成员,而不是在代码编写中添加更多“抽象”?
您当然可以直接调用成员,但是您将无法使用<< 链接后续表达式。让每个操纵器返回对被操纵的std::ostream(或std::istream)对象的引用是允许链接的原因。成员方法不返回此类引用。
例如:
cout << setprecision(5) << 123.45 << endl;
翻译成这样:
operator<<(cout, setprecision(5)).operator<<(123.45).operator<<(&endl);
最终会在内部调用这样的东西:
//operator<<(cout, setprecision(5));
cout.precision(5);
//cout.operator<<(123.45);
use_facet<num_put<char>>(cout.getloc()).put(
ostreambuf_iterator it{cout},
cout,
cout.fill(),
123.45
);
//cout.operator<<(&endl)
endl(cout);
不像使用<< 重载那么漂亮,是吗?