【问题标题】:What is in reality "endl" (or any output manipulator)? How is it implemented and how does it work with operator<<?实际上“endl”(或任何输出操纵器)是什么?它是如何实现的以及它如何与 operator<< 一起工作?
【发布时间】:2020-10-08 23:55:55
【问题描述】:

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

这些“输出操纵器”可以使用库iomanip 访问,但在执行诸如cout &lt;&lt; setprecision(5); 之类的命令时实际发生了什么

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

谢谢!

【问题讨论】:

  • endl 是“新行”+flush(),它们是函子(或某种)
  • "abstraction" 允许链接:std::cout &lt;&lt; "hello" &lt;&lt; std::endl; 而不是 std::cout &lt;&lt; "hello"; std::endl(std::cout);
  • @Jarod42 所以返回类型是 ostream& ?我真的很想知道是否可以定义没有参数的函数,这些函数可以在不使用语法function()(不带括号)的情况下调用
  • 这些方法确实有参数,但它们恰好匹配后来通过ostream::operator&lt;&lt;传入的参数。因此,您本质上是在传递对它们的引用。 (这些是恒等函数,实现为函子。它们接收一个 ostream,对其进行变异并返回它)

标签: c++ output iomanip


【解决方案1】:

endl 到底是什么?当然,它会打印一个新行并刷新 ostream 缓冲区。但实际上它是什么?

std::endl 是一个函数。它

在输出序列 os 中插入一个换行符并刷新它


程序员可以定义像endl这样的“实体”吗?

是的。

这是一个演示程序。

#include <iostream>

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

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

及其输出。

In test_manip

【讨论】:

  • “In”发生了什么?
  • 谢谢!但这仅在使用 cout 时有效吗?
  • @Some1,它适用于所有 std::ostream 对象。应该适用于std::ofstreamstd::ostringstream
【解决方案2】:

endl 实际上是什么?当然,它会打印一个新行并刷新 ostream 缓冲区。但实际上它是什么?

std::endl 是一个函数,它将std::ostream&amp; 引用作为输入并返回std::ostream&amp; 引用作为输出:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

std::basic_ostream::operator&lt;&lt; 具有接受指向此类函数的指针的重载:

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&lt;CharT,Traits&gt;&amp; (*)(std::basic_ostream&lt;CharT,Traits&gt;&amp;))匹配的函数都可以传递给operator&lt;&lt;

执行命令时实际发生了什么,例如:cout &lt;&lt; setprecision(5);

setprecision() 看起来像一个函数调用

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

为了将用户输入应用到std::ostream 对象(或std::istream 对象),这样的操纵器返回一个实现定义类型的实例,该实例保存输入,并且然后重载非成员 operator&lt;&lt; 以采用该类型。当调用该重载时,它可以根据需要将输入应用于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 读取)。

为什么干脆不使用相应的函数成员,而不是在代码编写中添加更多“抽象”?

您当然可以直接调用成员,但是您将无法使用&lt;&lt; 链接后续表达式。让每个操纵器返回对被操纵的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);

不像使用&lt;&lt; 重载那么漂亮,是吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-01
    • 2013-09-27
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2014-11-22
    • 2013-01-18
    • 1970-01-01
    相关资源
    最近更新 更多