"endl"(或任何输出操纵器)实际上是什么?它是如何实现的以及它如何与 operator<< 一起工作?

What is in reality "endl" (or any output manipulator)? How is it implemented and how does it work with operator<<?

实际上 endl 是什么?当然,它会打印一个新行并刷新 ostream 缓冲区。但它实际上是什么?像endl这样的"entities"可以由程序员来定义吗?

有这些 "output manipulators" 可以使用库 iomanip 访问,但执行命令时实际发生的情况如下: cout << setprecision(5);

setprecision() 看起来像一个函数调用,但在使用 cout 实例时没有打印任何内容。它改变了精度,但为什么干脆不使用相应的函数成员而不是在代码编写中添加更多"abstraction"?我所说的抽象是指非直观代码。

谢谢!

What is actually endl? Of course, it prints a new line and flushes the ostream buffer. But what is it actually?

std::endl 是一个函数。它

Inserts a newline character into the output sequence os and flushes it


Can such "entities" like endl be defined by the programer?

是的。

这是一个演示程序。

#include <iostream>

std::ostream& test_manip(std::ostream& out)
{
   return (out << "In test_manip\n");
}

int main()
{
   std::cout << test_manip;
}

及其输出。

In test_manip

What is actually endl? Of course, it prints a new line and flushes the ostream buffer. But what is it actually?

std::endl 是一个函数,它将 std::ostream& 引用作为输入,returns 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)

Can such "entities" like endl be defined by the programer?

是的。任何匹配上述签名 (std::basic_ostream<CharT,Traits>& (*)(std::basic_ostream<CharT,Traits>&)) 的函数都可以传递给 operator<<.

what is actually going on when executing a command such as: cout << setprecision(5);

setprecision() looks like a function call

一个函数调用。 I/O 接受用户输入的操纵器与 I/O 不接受任何用户输入的操纵器的工作方式略有不同。

为了将用户输入应用到 std::ostream 对象(或 std::istream 对象),这样的操纵器 returns保存输入的实现定义类型的实例,然后重载非成员 operator<< 以采用该类型。当调用该重载时,它可以根据需要将输入应用到 std::ostream(或 std::istream)。

std::setprecision() 的情况下,它需要一个 int 作为输入,并且 return 是一个实现定义的类型,它包含 int,然后type 将 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)

yet nothing is printed when using the cout instance. It changes the precision

正确,因为 set::setprecision() return 的操纵器不会向 std::ostream 的输出缓冲区写入任何内容,它只是调整 std::ostream 本身。

没有什么可以阻止 I/O 操纵器写入 std::ostream(或从 std::istream 读取),如果它愿意的话。

why simply not use the corresponding function member instead of adding more "abstraction" to code writing?

您当然可以直接调用成员,但是您将无法使用 << 链接后续表达式。让每个操纵器 return 引用 std::ostream(或 std::istream)被操纵的对象是允许链接的原因。成员方法不return这样的引用。

例如:

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);

不如使用 << 重载那么漂亮,是吗?