如何正确地重载 ostream 的 << 运算符?[复制]

How can I properly overload the << operator for an ostream? [duplicate]

提问人:Matthias van der Vlies 提问时间:1/25/2009 最后编辑:Peter MortensenMatthias van der Vlies 更新时间:10/9/2023 访问量:463573

问:

我正在用C++编写一个用于矩阵运算的小型矩阵库。但是,我的编译器抱怨,以前没有。这段代码在架子上搁置了六个月,在此期间,我将计算机从 Debian 4.0 (Etch) 升级到 Debian 5.0 (Lenny) (g++ (Debian 4.3.2-1.1) 4.3.2)。但是,我在具有相同 g++ 的 Ubuntu 系统上遇到了同样的问题。

这是我的矩阵类的相关部分:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix);
    }
}

而“实现”:

using namespace Math;

std::ostream& Matrix::operator <<(std::ostream& stream, const Matrix& matrix) {

    [...]

}

这是编译器给出的错误:

matrix.cpp:459:错误:“std::ostream& Math::Matrix::operator<<(std::ostream&, const Math::Matrix&)' 必须取 正好一个参数

我对这个错误有点困惑,但是在这六个月做了很多Java之后,我的C++又变得有点生疏了。:-)

C++ 命名空间 运算符重载 IOSTREAM ostream

评论


答:

145赞 Mehrdad Afshari 1/25/2009 #1

您已将函数声明为 .它不是类的成员。您应该从实现中删除。 表示指定的函数(不是类的成员)可以访问私有成员变量。您实现该函数的方式类似于类的实例方法,这是错误的。friendMatrix::friendMatrix

评论

9赞 David Rodríguez - dribeas 3/24/2009
您还应该在 Math 命名空间中声明它(而不仅仅是使用 using 命名空间 Math)。
1赞 Mark Lakata 5/2/2015
为什么必须在 的命名空间中?似乎它应该在全局命名空间中。我同意我的编译器希望它位于 的命名空间中,但这对我来说没有意义。operator<<MathMath
0赞 Patrick 8/3/2019
对不起,但我不明白为什么我们在这里使用朋友关键字?当在类中声明友元运算符覆盖时,似乎我们无法用 Matrix::operator<<(ostream& os, const Matrix& m) 实现。相反,我们只需要使用全局运算符覆盖运算符<<ostream&os,const Matrix&m),那么为什么还要费心在类中声明它呢?
0赞 Erik 10/14/2022
帕特里克,您可以使用实现来访问私有(和受保护的)成员变量。例如,您可能希望打印矩阵的所有元素,这些元素可能是私有的。friend
93赞 kal 1/25/2009 #2

补充一下 Mehrdad 的回答

namespace Math
{
    class Matrix
    {
       public:

       [...]


    }
    std::ostream& operator<< (std::ostream& stream, const Math::Matrix& matrix);
}

在实施中

std::ostream& operator<<(std::ostream& stream,
                     const Math::Matrix& matrix) {
    matrix.print(stream); // Assuming you define print for matrix
    return stream;
 }

评论

4赞 kal 1/25/2009
我不明白为什么这是一个反对票,这澄清了您可以声明运算符在命名空间中,甚至不能作为朋友,以及如何声明运算符。
2赞 kal 1/25/2009
Mehrdad 答案没有任何代码片段,所以我只是通过将其移动到命名空间本身的类之外来添加可能有效的代码。
0赞 Matthias van der Vlies 1/25/2009
我明白你的意思,我只看了你的第二个片段。但现在我看到你把操作员带出了课堂。谢谢你的建议。
7赞 David Rodríguez - dribeas 3/24/2009
它不仅不在类中,而且在 Math 命名空间正确定义。此外,它还有一个额外的优势(可能不是针对矩阵,而是针对其他类),即“打印”可以是虚拟的,因此打印将发生在最派生的继承级别。
157赞 Johannes Schaub - litb 1/25/2009 #3

我只是告诉你另一种可能性:我喜欢使用朋友定义:

namespace Math
{
    class Matrix
    {
    public:

        [...]

        friend std::ostream& operator<< (std::ostream& stream, const Matrix& matrix) {
            [...]
        }
    };
}

该函数将自动定位到周围的命名空间中(即使其定义出现在该类的范围内),但除非您使用 Matrix 对象调用 operator<<否则将不可见,这将使参数相关的查找找到该运算符定义。这有时有助于处理不明确的调用,因为它对 Matrix 以外的参数类型是不可见的。在编写其定义时,您还可以直接引用 Matrix 中定义的名称和 Matrix 本身,而无需使用一些可能很长的前缀来限定名称,并提供模板参数,例如 。MathMath::Matrix<TypeA, N>

80赞 sanjivr 2/11/2012 #4

假设我们谈论的是所有派生自的类的重载来处理类(而不是类的重载),那么在标头的 Math 命名空间之外声明重载函数更有意义。operator <<std::ostreamMatrix<<Matrix

仅当无法通过公共接口实现该功能时,才使用友元函数。

矩阵.h

namespace Math { 
    class Matrix { 
        //...
    };  
}
std::ostream& operator<<(std::ostream&, const Math::Matrix&);

请注意,运算符重载是在命名空间外部声明的。

矩阵 .cpp

using namespace Math;
using namespace std;

ostream& operator<< (ostream& os, const Matrix& obj) {
    os << obj.getXYZ() << obj.getABC() << '\n';
    return os;
}

另一方面,如果您的过载函数确实需要成为朋友,即需要访问私有和受保护的成员。

数学.h

namespace Math {
    class Matrix {
        public:
            friend std::ostream& operator<<(std::ostream&, const Matrix&);
    };
}

您需要用命名空间块将函数定义括起来,而不仅仅是 .using namespace Math;

矩阵 .cpp

using namespace Math;
using namespace std;

namespace Math {
    ostream& operator<<(ostream& os, const Matrix& obj) {
        os << obj.XYZ << obj.ABC << '\n';
        return os;
    }                 
}

评论

1赞 LeonTheProfessional 7/9/2020
这里有点吹毛求疵..在这种情况下,我发现这是一个糟糕的缩写(它与“操作系统”绑定得太多)os
0赞 KcFnMi 10/7/2022
可以把超载放在(声明和实现)在班级里,我们不谈友谊吗?operator<<
49赞 QuentinUK 3/5/2016 #5

C++14 中,您可以使用以下模板打印任何具有 T::p rint(std::ostream&)const;成员。

template<class T>
auto operator<<(std::ostream& os, T const & t) -> decltype(t.print(os), os)
{
    t.print(os);
    return os;
}

C++20 中,可以使用概念

template<typename T>
concept Printable = requires(std::ostream& os, T const & t) {
    { t.print(os) };
};

template<Printable T>
std::ostream& operator<<(std::ostream& os, const T& t) {
    t.print(os);
    return os;
}

评论

0赞 barney 5/16/2016
有趣的解决方案!一个问题 - 这个运算符应该在哪里声明,比如在全局范围内?我认为它应该对可用于模板化它的所有类型都是可见的?
0赞 QuentinUK 5/17/2016
@barney 它可能与使用它的类一起位于您自己的命名空间中。
0赞 Jean-Michaël Celerier 3/9/2017
你不能直接返回,因为它无论如何都是返回类型?std::ostream&
7赞 QuentinUK 3/12/2017
@Jean-MichaëlCelerier decltype 确保仅当存在 t::p rint 时才使用此运算符。否则,它将尝试编译函数体并给出编译错误。
0赞 QuentinUK 5/4/2020
已添加概念版本,在此处测试 godbolt.org/z/u9fGbK
4赞 Hemanth Kollipara 5/28/2021 #6

我想用一个重载以打印数组的示例来简化这一点。<<

  1. 首先在运算符周围传递两种对象类型<<

  2. 创建一个函数来重载运算符,如下所示。

    #include <iostream>
    using namespace std;
    
    void operator<<(ostream& os, int arr[]) {
        for (int i = 0;i < 10; i++) {
            os << arr[i] << " ";
        }
        os << endl;
    }
    
    int main() {
        int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        cout << arr;
    }
    

如果还需要级联运算符,请确保返回一个对象 在重载函数中,如下所示,cout

#include <iostream>
using namespace std;

ostream& operator<<(ostream& os, int arr[]) {
    for (int i = 0;i < 10; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    return os;
}

int main() {
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int arr2[10] = { 11, 22, 33, 44, 55, 66, 77, 88, 99, 100 };
    // Cascading of operators
    cout << arr << arr2;
}

评论

1赞 QuentinUK 7/8/2021
您可以让它与任何大小的数组一起使用:-template<int N> ostream& operator<<(ostream& os, int(& arr)[N]) { etc
2赞 mcleod_ideafix 6/1/2022
不应该代替函数内部吗?oscoutoperator==