输入输出运算符的重载
运算符重载一般有两种形式:重载为类的成员函数和重载为类的友元元函数。接下来我们就对这两种重载形式进行详细讲解。
1、重载为类的成员函数
在上一小节重载“+”、“-”运算符时,就是重载成了类的成员函数。重载为成员函数,它就可以自由地访问本类的数据成员。运算的操作数会以调用者或参数的形式表示。
如果是双目运算符重载为类的成员函数,则它有两个操作数,左操作数是对象本身的数据,由this指针指出,右操作数则通过运算符重载函数的参数表来传递。其一般调用格式如下所示:
左操作数.运算符重载函数(右操作数);
如上一节案例中重载“+”运算符,当调用a1+a2时,其实就相当于函数调用a1.oprerator+(a2)。
如果是单目运算符重载为类的成员函数,则要分为前置与后置运算符,如果是前置运算符,则它的操作数是函数调用者,函数没有参数,其一般调用格式如下所示:
操作数.运算符重载函数();
如上一节案例中如果重载前置单目运算符“++”,则++a1的调用其实就相当于函数调用a1.operator++()。
如果是后置单目运算符,则函数要带一个整形参数,这个整型参数不起任何作用,只是用来区分前置与后置。
为了加深读者的理解,接下来通过一个案例来演示前置“++”与后置“++”运算符的重载,具体代码如例1所示。
例1
1 #define _CRT_SECURE_NO_WARNINGS
2 #include <iostream>
3 using namespace std;
4 class A
5 {
6 private:
7 int x;
8 int y;
9 public:
10 A(int x1 = 0, int y1 = 0):x(x1), y(y1){}
11 void show() const; //输出数据
12 A operator++(); //重载前置++
13 A operator++(int); //重载后置++
14 };
15
16 void A::show() const
17 {
18 cout << "(x,y) = " << "(" << x << "," << y << ")" << endl;
19 }
20 A A::operator++() //前置++实现
21 {
22 ++x;
23 ++y;
24 return *this;
25 }
26 A A::operator++(int) //后置++实现
27 {
28 A a = *this;
29 ++(*this); //调用已经实现的前置++
30 return a;
31 }
32 int main()
33 {
34 A a1(1, 2), a2(3, 4);
35 (a1++).show();
36 (++a2).show();
37
38 system("pause");
39 return 0;
40 }
运行结果如图1所示。
图1 例1运行结果
在例1中创建了两个对象a1,a2,a1调用后置“++”,a2调用前置“++”,由图4-3打印结果所知,a1先打印出了结果后执行“++”,而a2先执行了“++”运算后打印了结果。
在实现前置“++”时,数据成员进行自增运算,然后返回当前对象(即this指针所指向的对象)。而实现后置“++”时,创建了一个临时对象来保存当前对象的值,然后再将当前对象自增,最后返回的是保存了初始值的临时对象。
注意:前置单目运算符与后置单目运算符重载的最主要区别是函数的形参,后置单目运算符带一个int型形参,但它只起区分作用。
2、重载为类的友元函数
除了可以重载为类的成员函数外,运算符还可以重载为类的友元函数,重载为类的友元函数只是在函数前加一个friend关键字,其格式如下所示:
friend 返回类型 operator 运算符(参数列表)
{
函数体;
}
重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右保持一一对应。
重载为类的友元函数,则调用运算符的一般格式如下所示:
operator 运算符(参数1,参数2);
例如调用a1+a2其实相当于函数调用operator+(a1,a2)。
为了加深读者的理解,接下来改写例4-1,将“+”、“-”运算符重载为类的友元函数,具体代码如例2所示。
例2
1 #include <iostream>
2 using namespace std;
3 class A
4 {
5 private:
6 int x;
7 int y;
8 public:
9 A(int x1 = 0, int y1 = 0):x(x1), y(y1){}
10 void show() const; //输出数据
11 friend A operator+(const A& a1, const A& a2) ; //重载为类的友元函数
12 friend A operator-(const A& a1, const A& a2); //重载为类的友元函数
13 };
14 void A::show() const
15 {
16 cout << "(x,y) = " << "(" << x << "," << y << ")" << endl;
17 }
18 A operator+(const A& a1, const A& a2)
19 {
20 return A(a1.x + a2.x, a1.y + a2.y);
21 }
22 A operator-(const A& a1, const A& a2)
23 {
24 return A(a1.x - a2.x, a1.y - a2.y);
25 }
26 int main()
27 {
28 A a1(1, 2);
29 A a2(4, 5);
30 A a;
31 cout << "a1: ";
32 a1.show();
33 cout << "a2: ";
34 a2.show();
35 a = a1 + a2;
36 cout << "a: "; a.show();
37
38 a = a1 - a2;
39 cout << "a: "; a.show();
40
41 system("pause");
42 return 0;
43 }
运行结果如图2所示。
图2 例2运行结果
例2将“+”、“-”运算符重载为类A的友元函数,则运行结果和例1相同,证明两种重载形式都可以完成运算符的重新定义。
在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:
(1)一般情况下,单目运算符最好重载为类的成员函数,双目运算符最好重载为类的友元函数。
(2)若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。
(3)若运算符的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。
(4)具有对称性的运算符可能转换任意一端的运算对象,如算术、关系运算符等,通常重载为非成员函数。
(5)有4个运算符必须重载为类的成员函数:赋值(=)、下标([])、调用(())、成员访问箭头(->)。