【问题标题】:Why and how to overload operator<< for printing为什么以及如何重载 operator<< 以进行打印
【发布时间】:2015-03-08 09:28:05
【问题描述】:

我已经编写了一个实现堆栈的程序。而且我有一个显示功能。

我一开始是这样写显示函数的:

template <class t>
void Mystack<t>::display()
{
    for (int i = 0; i <= top; i++)
    {
        std::cout << input[i] << " ";
    }
}

然后开发人员建议我编写一个更通用的显示函数。所以我把显示函数写成:

template <class T>
void Mystack<T>::display(std::ostream &os) const         
{
    for (int i = 0; i <= top; i++)
    {
        os << input[i] << " ";
    }
}

据我了解,编写上述函数的好处是现在我有一个通用的显示函数,我可以使用它来将数据显示到控制台或文件中。

问题 1:我的理解正确吗?

现在另一个建议是编写如下函数:

template <typename T>
friend std::ostream& operator<<(std::ostream& s, Mystack<T> const& d) {
    d.display(s);
    return s;
}

问题2:以上显示功能有什么好处?有了上面的显示功能,我到底能实现什么?

【问题讨论】:

  • 你可以写std::cout &lt;&lt; d;而不是d.print(std::cout)。 (您可以将std::cout 替换为另一个输出流,例如文件)。
  • 我认为你的问题很好,但标题有点误导。 “为什么以及如何重载运算符
  • 我改了问题。
  • 回复:问题2,是的,做吧!由于您做得对,它会“正常工作”的另一个地方是日志记录。使用一个流行的记录器的例子,LOG4CPLUS_DEBUG(m_logger, "stack has "
  • @KennyOstrom 完全不知道"stack has " &lt;&lt; stack 是如何工作的。 "stack has " 是 C 风格的字符串文字,而不是 std::ostream

标签: c++ operator-overloading friend


【解决方案1】:

对于第1题,你的理解是正确的,但真正的进步来自于第2题的写作建议:

template <typename T>
friend std::ostream& operator<<(std::ostream&, Mystack<T> const& );

这将使任何人都可以像流式传输其他任何内容一样流式传输您类型的对象:

std::cout << "Hi, my stack is " << stack << ", it has size " << stack.size();

到他们想要的任何流:

some_file << "Result of computation is: " << stack;
std::cerr << "Error, invalid stack: " << stack << ", expected: " << some_other_thing;

【讨论】:

  • 我认为在operator&lt;&lt; 中调用单独的display() 函数的主要优点是可以在类层次结构中的任何地方使用operator&lt;&lt;,在基类中只编写一次并标记display()函数virtual,如果我错了,请纠正我。
  • @vsoftco 我看不出写void Derived::display(std::ostream&amp;)std::ostream&amp; operator&lt;&lt;(std::ostream&amp;, Derived const&amp;) 的区别。无论哪种方式,派生类都需要自己的方法吗?
  • 是的,确实,但是出于“风格”的原因,您可能只想使用operator&lt;&lt; 来显示内容,并避免重新定义它。此外,这允许层次结构中的某些类不覆盖display() 函数,因此我认为总体上该解决方案更优雅一些。事实上,派生类可能根本不会选择覆盖display(),这种方法会奏效。否则,您仍然需要在层次结构中的每个类中定义 operator&lt;&lt;
  • @vsoftco 派生类也可以使用基类的 operator&lt;&lt;,如果他们不想覆盖它。
  • 刚刚意识到我在考虑operator&lt;&lt;friend(或全局)实现,因为通常你不想让operator&lt;&lt; 成为成员函数。如果不在 Derived 类中明确地重新定义 operator&lt;&lt;,你会怎么做?
【解决方案2】:

首先 - 是的。通过使用std::ostream&amp; 参数,您也可以输出到任何派生流,例如std::ofstream,或std::coutstd::cerr

使用operator&lt;&lt; 允许您使用该运算符。考虑:

mystack<int> stackOfInts;
//...
std::cout << "Stack contents:" << std::endl << stackOfInts << std::endl;

它比“标准”函数调用更惯用。

返回流允许运算符的链接,如上例所示。链接实际上是将调用 operator&lt;&lt; 的结果传递给另一个:

operator<<( operator<<("Stack contents:", std::endl), stackOfInts ) );

如果这个重载调用也没有返回std::ostream&amp;,那么就没有办法了:

operator<<( resultOfAbove, std::endl );

将函数声明为friend 允许其定义使用私有成员。如果没有这个,您将不得不为每个私有成员编写一个公共 getter。

【讨论】:

  • 我们为什么要使用好友标识符?
  • @tanz 将函数声明为friend 允许定义使用private 成员。如果要打印出其他方式无法公开访问的信息,则很有用。 (通常情况下)。
【解决方案3】:

对于问题 2,如果您有一个基类和许多派生类,并且只想在基类中编写一次 operator&lt;&lt;,则可以采用这种方式。然后,如果沿类层次结构将display() 函数声明为virtual,则可以在运行时选择正确的显示函数。也就是说,如果你有类似的东西

Derived foo;
std::cout << foo;

然后Base::operator&lt;&lt; 将调用Derived::display(),因为它被标记为虚拟并且foo 通过引用传递。当您有一个类层次结构并且不想为每个派生类重载operator&lt;&lt; 时,这是要走的路。

这是避免代码重复的常用技巧。见

Making operator<< virtual?

在 StackOverflow 上了解更多详情。

【讨论】:

    【解决方案4】:

    两者的显示功能基本相同。不同的是你调用函数的方式。 使用第一个函数,您可以以通常的方式调用函数:

    std::cout<<"This is MyStack (with first method): ";
    m.display(std::cout);      //function call
    std::cout<<std::endl;
    

    使用第二个函数,您使用运算符“

    std::cout<<"This is MyStack (with second method): "
                   <<m   //function call
                   <<std::endl;
    

    但我个人更喜欢第二个。因为它对我来说更熟悉。

    【讨论】:

    • 我们为什么要使用好友标识符?
    • @tanz__通常重载的 'operator
    猜你喜欢
    • 2012-01-18
    • 2019-02-24
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 2018-04-06
    • 2016-03-20
    • 2014-02-06
    相关资源
    最近更新 更多