4

我正在自学 C++。我正在研究运算符重载,我能够理解加减运算符重载。但是 I/O 操作符的重载有点令人困惑。我为复数创建了一个类,现在我正在重载运算符。

来自 Complex.h 的函数原型

friend ostream& operator<<(ostream&, const Complex&);

来自 Complex.cpp 的函数

ostream& operator<<(ostream& os, const Complex& value){
os << "(" << value.r <<", "
<< value.i << ")" ;
return os;
}
  1. 谁能解释(在基本层面上)为什么我们必须在这里使用友元函数声明?
  2. 为什么我们必须通过引用传递所有参数和运算符的返回类型?
  3. 这个函数在不使用 const 的情况下也能正常工作,但为什么我们在这里使用 const 呢?将 Complex 作为常量引用传递有什么好处?
4

6 回答 6

3

您不必流媒体运营商成为朋友。它必须外部声明,因为 Complex 对象位于运算符的右侧。

但是,如果您的 Complex 类有办法访问所需的成员(可能通过 getter),您可以让流操作符使用它们。说:

std::ostream& operator<<( std::ostream& os, Complex const& cpx )
{
   return os << cpx.getReal() << ',' << cpx.getImaginary();
}

您的operator/重载可以作为内部函数完成,但实际上最好也作为具有两个 const& 参数的外部函数来实现。如果它是成员函数,则它必须是 const 成员。你的不是。

你可以基于operator /=这样来实现它

Complex operator/ ( Complex const& lhs, Complex const& rhs )
{
    Complex res( lhs );
    res /= rhs; // or just put return in front of this line
    return res; 
}
于 2014-07-13T14:39:38.217 回答
2

对于friend ostream& operator<<(ostream&,const Complex&);

  1. 因为您在这里声明了一个自由函数,并希望它访问Complex对象的内部(私有/受保护)。对于这些重载,具有“友元免费功能”是很常见的,但肯定不是强制性的。

  2. 因为流是不可复制的(它没有意义,另请参阅这篇文章),按值传递需要一个副本。按值传递Complex也需要一个无用的副本。

  3. 因为这些输出运算符不应该修改他们正在处理的对象(输入运算符显然是),所以添加const以确保。

于 2014-07-13T13:51:24.763 回答
1
  1. 谁能解释(在基本层面上)为什么我们必须在这里使用友元函数声明?

    如果您将一个类声明为friend of另一个类,那么您是在说朋友类可以访问您的类的私有和受保护属性和函数。
    在您的情况下,您声明ostream& operator<<为朋友,这意味着在该函数的主体内,ostream 类将能够访问您的复杂类的私有和受保护函数和属性。

  2. 为什么我们必须通过引用传递所有参数和运算符的返回类型?

    因为它是这样写的,以避免复制 ostream 对象。因此,您的<<重载将使用相同的对象,而无需在每次使用时复制。

  3. 这个函数在不使用 const 的情况下也能正常工作,但为什么我们在这里使用 const 呢?将 Complex 作为常量引用传递有什么好处?

    const在函数之后意味着您没有更改类的任何属性,而您没有更改。

我认为这是正确的,我希望它有所帮助,但如果有人想发表评论,请随意。

于 2014-07-13T13:51:30.933 回答
0
friend ostream& operator<<(ostream&, const Complex&);

这里函数需要成为朋友,因为您可能需要能够访问复杂的私有成员。

当您<<Complex类重载时,您可能需要Complex在重载函数中访问类的数据成员,并且数据成员可能是私有的。如果它们是私有的,要访问其中的数据成员,operator <<您需要使其成为complex类的成员,这是不可能的,因为运算符的左侧对象是ostream类类型,因此唯一的选择是使其成为朋友Complex<<使用全局作用域函数。

现在对象通过引用返回,以避免创建其他人告诉的对象的多个副本。并且必须返回该对象,因为它将支持当操作员用于像 `cout< 这样的级联写入时

参数作为 a 传递,const因为在接收对象时总是一个好习惯,就const好像它不会在函数内部更改一样。这使它适用于偶数const对象。

于 2014-07-13T13:52:40.080 回答
0

对于非友元成员函数运算符,只有一个参数,即运算符的右侧。因此,对于您使用除法运算符的示例,您可以这样做:

Complex a = ...;
Complex b = ...;

Complex c = a / b;

并且编译器实际上会将最后一个视为

Complex c = a.operator/(b);

这意味着例如输出操作符,如果它是一个普通的成员函数,你“输出”到类的一个实例。换句话说,它会像

Complex a = ...;
Complex b = ...;
a << b;  // Really a.operator<<(b);

显然,这不是实现应该输出到流的运算符时想要的。因此我们需要使用一个非成员函数,它可以接受两个不同的参数。


对于第二点,请记住传递参数的默认方法是按值,这意味着值是被复制的。对于较小的类,这无关紧要,但如果有很多数据,或者该类有一个复杂的复制构造函数,那么这可能会对性能造成相当大的影响。此外,某些类型和类无法复制,例如std::istream,您必须通过引用传递它们。


最后一点,通过创建一些参数const,您可以告诉编译器和函数的用户它不会更改参数。除了让函数的用户更容易知道调用函数不会有任何副作用之外,它还允许编译器进行一些优化。


一般来说,关于运算符重载,您可能需要阅读有关该主题的参考资料

于 2014-07-13T13:55:29.753 回答
0

谁能解释为什么我们必须在这里使用朋友功能?

一个类的 A可以从它被指定为 afriend的类访问private或访问变量/函数。如果需要访问任何私有功能,则必须将 I/O 操作符设为类。如果不是,则可以简单地在类之外定义函数而不这样声明它。protectedfriendfriendComplex

为什么我们必须通过引用传递运算符?

我不确定你是什么意思。运算符是我们定义的函数,而不是在任何地方传递的参数。你指的是它需要的参数吗?好吧, std::ostream被视为引用类型,因为按值获取它会导致复制,这是流类不允许的。试试看,你会得到一个编译器错误。我将在下面讨论为什么Complex将其作为参考类型。

这个函数不用 using 也能正常工作const,但我们为什么要const在这里使用呢?Complex作为常量对象传递有什么好处?

首先,为什么使用const?类型修饰符是对编译器的const明确声明,我们不会以任何方式更改对象。它还使代码的维护者清楚。此外,写出一个对象通常需要该值,这意味着不需要修改该对象。

使用对的引用还允许您将右值或临时变量传递给函数,这对于仅采用左值的-const的引用是不可能的。例如:const

struct S { };

void f(S&);
void h(S const&);

int main()
{
    S s;

    f(s); // OK
    f(S()); // ERROR!

    h(s); // OK
    h(S()); // OK
}
于 2014-07-13T13:58:36.740 回答