为什么以及如何重载操作员<<进行打印

Why and how to overload operator<< for printing

本文关键字:lt 操作员 打印 重载 何重载 为什么      更新时间:2023-10-16

我写了一个实现堆栈的程序。我有一个显示功能。

这就是我最初编写显示函数的方式:

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:拥有上述显示功能有什么好处?通过上述显示功能,我究竟可以实现什么?

对于问题 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;

首先 - 是的。通过获取 std::ostream& 参数,您也可以输出到任何派生流,如 std::ofstreamstd::coutstd::cerr

使用 operator<< 允许您使用该运算符。考虑:

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

它只是比"标准"函数调用更惯用。

返回流允许链接运算符,如上例所示。链接有效地将调用operator<<的结果传递到另一个调用中:

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

如果此重载调用不返回std::ostream&,则无法执行以下操作:

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

将函数声明为friend允许其定义使用私有成员。如果没有这个,你将不得不做一些事情,比如为每个私人成员编写一个公共获取器。

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

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;

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

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

Derived foo;
std::cout << foo;

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

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

让操作员<<虚拟?

在 StackOverflow 上了解更多详情。