C++面向对象程序设计
参考书目:《C++面向对象程序设计》—— 谭浩强 《C++程序设计:思想与方法》—— 翁惠玉
第四章:对运算符进行重载
一、运算符重载的定义
与函数重载类似,对已有的运算符赋予新的含义,用一个运算符表示不同功能的运算,这就是运算符重载。如<<是C++的移位运算符,它与流对象cout配合作为流插入运算符,这是C++对<<进行了重载处理。C++通过重新定义运算符,使它能够用于特定类的对象执行特定的功能。
运算符重载的实质:
- 必要性:C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)。
- 实现机制:将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。
例:通过成员函数实现复数的加法。
class Complex
{
private:
double real;
double imag;
public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
Complex complex_add(Complex &c2);
void display();
} ;
Complex Complex::complex_add(Complex &c2)
{ Complex c;
c.real=real +c2.real;
c.imag=imag+c2.imag;
return c;
}
void Complex::display()
{cout<<"("<<real<<","<<imag<<"i)"<<endl;}
int main()
{
Complex c1(3,4),c2(5,-10),c3;
c3=c1.complex_add(c2);
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
在Complex类中定义了complex_add函数做加法,函数的参数是引用对象,作为一个加数。
在函数里定义了临时对象c,两个赋值语句相当于:
c.real = this->real +c2.real;
c.imag = this->imag+c2.imag;
在main函数中通过对象c1调用加法函数,上面的语句相当于:
c.real = c1.real +c2.real;
c.imag = c1.imag+c2.imag;
二、运算符重载的方法
运算符重载的方法是定义一个重载运算符函数,在需要时系统自动调用该函数,完成相应的运算。运算符重载实质上是函数的重载。
运算符重载函数格式:数据类型 operator 运算符(形参表){ 重载处理 }
例:重载运算符+,用于两个复数相加。
class Complex
{
public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r; imag=i;}
Complex operator + (Complex &c2);
void display();
private:
double real;
double imag;
};
Complex Complex::operator + (Complex &c2)
{
Complex c;
c.real=real+c2.real;
c.imag=imag+c2.imag;
return c;
}
void Complex::display()
{ cout<<"("<<real<<","<<imag<<"i)"<<endl; }
int main()
{
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
cout<<"c1=";
c1.display();
cout<<"c2=";
c2.display();
cout<<"c1+c2=";
c3.display();
return 0;
}
说明:
(1)用运行符重载函数取代了前例中的加法成员函数,从外观上看函数体和函数返回值都是相同的。
(2)在主函数中的表达式c3=c2+c1 取代了前例中的c3=c1.complex_add(c2) ,编译系统将表达式c3=c1+c2 解释为c1.operator + (c2)
对象c1调用的重载函数operator + ,以c2为实参计算两个复数之和。
重载运算符与一般函数的比较:
相同:1)均为类的成员函数;2)实现同一功能
差别:
一般函数
void AddA(A &a, A &b)
{
i=a.i+b.i;
}
函数调用:a3.AddA(a1,a2);由对象a3调用。
重载运算符
A operator +(A &a)
{
A t;
t.i=i+a.i;
return t;
}
函数调用:a3=a1.operator+(a2);由对象a1调用。
重载运算符即重新定义运算符,由左操作符调用右操作符。最后将函数返回值赋给运算结果的对象。
三、运算符重载的规则
运算符重载的规则:
- C++只允许已有的部分运算符实施重载,不可臆造新的。
- 不能重载的运算符有五个: . * :: ?: sizeof
- 重载不改变操作数的个数和语法结构。
- 重载不改变运算符的优先级和结合性。
- 运算符重载函数不能带默认值参数。
- 运算符重载函数必须与自定义类型的对象联合使用,其参数至少有一个类对象或类对象引用。
- C++默认提供 = 和 & 运算符重载。
- 理论上可以将运算符重载为任意操作。实际中应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能 。
- 运算符重载函数可以是类成员函数也可以是类的友元函数,还可以是普通函数。
- C++规定赋值运算符、下标运算符、函数调用运算符必须定义为类的成员函数;而输出流插入、输入流提取、类型转换运算符不能定义为类的成员函数。
四、运算符重载函数
例:将加法运算符重载为适用于复数加法,重载函数作为类的友元函数。
class Complex
{
public:
Complex () {real=0;imag=0;}
Complex (double r) {real=r;imag=0;}
Complex (double r,double i) {real=r;imag=i;}
friend Complex operator+ (Complex &c1,Complex &c2);
void display();
private:
double real;
double imag;
};
void Complex::display()
{cout<<"("<<real<<","<<imag<<"i)"<<endl;}
Complex operator+ (Complex &c1, Complex &c2)
{ return Complex(c1.real+c2.real, c1.imag+c2.imag) ; } // 显式调用构造函数
int main()
{
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
cout<<"c1=";
c1.display();
cout<<"c2=";
c2.display();
cout<<"c1+c2=";
c3.display();
return 0;
}
加法运算符重载为友元函数,C++在编译时将表达式c1+c2解释为 operator + ( c1, c2)
即相当于执行以下函数
Complex operator + ( Complex & c1, Complex & c2 )
{ return Complex( c1.real + c2.real , c1.imag + c2.imag ); }
因为普通函数是不能直接访问对象的私有成员,如果普通函数必须访问对象的私有成员,可调用类的公有成员函数访问对象的私有成员。
如想将一个复数和一个整数相加,运算符重载函数作为成员函数定义如下:
Complex Complex ::operator + ( int & i ) { return Complex( real + i , imag ); }
注意在运算符+的左侧必须是Complex类对象,程序中可以写成:c3 = c2 + n 不能写成:c3 = n + c2
如果要求在使用重载运算符时,运算符左侧操作数不是对象,就不能使用前面定义的运算符重载函数,可以将运算符重载函数定义为友元函数:
friend Complex operator + ( int & i , Complex & c )
{ return Complex( c.real + i , c.imag ); }
友元函数不要求第一个参数必须是类类型,但是要求实参要与形参一一对应:c3 = n + c2 正确 c3 = c2 +n 错误
为了实现加法的交换率,必须定义两个运算符重载函数,记住成员函数要求运算符左侧的操作数必须是自定义类型的对象,而友元函数没有这个限制,可以用下面两个组合中任意一个:
- 成员函数(左操作数是对象,右操作数是非对象)、友元函数(左操作数是非对象,右操作数是对象)
- 友元函数(左操作数是对象,右操作数是非对象)、友元函数(左操作数是非对象,右操作数是对象)
由于使用友元会破坏类的封装,要尽量将运算符重载函数定义为成员函数。但考虑到各方面的因素和习惯,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。
五、重载双目运算符
双目的意思是运算符左边和右边的操作数均参加运算。如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)。
class room
{
float Length;
float Wide;
public:
room(float a = 0.0, float b = 0.0) { Length = a; Wide = b; }
void Show(void){cout << "Length=" << Length << '\t' << "Wide=" << Wide << endl;}
void ShowArea(void) { cout << "Area=" << Length * Wide << endl; }
room operator+(room &);//重载运算符+,函数原型
};
room room::operator + (room &r) //重载运算符,函数定义
{
room rr;
rr.Length = Length + r.Length;
rr.Wide = Wide + r.Wide;
return rr;
}
void main(void)
{
room r1(3, 2), r2(1, 4), r3, r4;
r1.Show();
r2.Show();
r3 = r1 + r2;
r3.Show();
r4 = r1 + r2 + r3;
r4.Show();
}
运算符的优先级和结合律是不能改变的!
六、重载单目运算符
单目运行符只要一个操作数,由于只有一个操作数,重载函数最多只有一个参数,如果将运算符重载函数定义为成员函数还可以不用参数。
例:有一个Time类,数据成员有时、分、秒。要求模拟秒表,每次走一秒,满60秒进位,秒又从零开始计数。满60分进位,分又从零开始计数。输出时、分和秒的值。
class Time
{
public:
Time() { hour = 0; minute = 0; sec = 0; }
Time(int h, int m, int s) :hour(h), minute(m), sec(s) {}
Time operator++();
void display() { cout << hour << ":" << minute << ":" << sec << endl; }
private:
int hour;
int minute;
int sec;
};
Time Time::operator ++() //前置单目运算符重载函数
{
sec++;
if (sec >= 60)
{
sec = sec - 60;
minute++;
if (minute >= 60)
{
minute = minute - 60;
hour++;
hour = hour % 24;
}
}
return *this;
}
int main()
{
Time time1(23, 59, 0);
for (int i = 0; i < 61; i++)
{
++time1;
time1.display();
}
return 0;
}
C++中除了有前++外,还有后++。同样的运算符由于操作数的位置不同,含义也不同。
- ++为前置运算时,它的运算符重载函数的一般格式为:operator ++( ){ …;}
- ++为后置运算时,它的运算符重载函数的一般格式为:operator ++(int){ …;}
class A
{
public:
float x;
A() { x = 1.5; }
A& operator++() { x++; return *this; }
A operator++(int){A a; a.x = this->x; x++; return a;}
A& operator--() { x--; return *this; }
A operator--(int){A a; a.x = this->x; x--; return a;}
};
void main(void)
{
A a1, a2;
a2 = a1--; cout << a2.x << endl;
a2 = --a1; cout << a2.x << endl;
a2 = ++a1; cout << a2.x << endl;
a2 = a1++; cout << a2.x << endl;
}
七、重载流插入和流提取运算符
cin和cout分别是istream类和ostream类的对象。 C++已经对>>和<<移位运算符进行了重载,使它们分别成为流提取运算符和流插入运算符。用户自定义类型的数据不能直接用<<和>>输出和输入,如想用它们进行输入或输出,程序员必须对它们重载。
重载函数原型的格式如下:
istream & operator >> (istream&,自定义类&);
ostream & operator << (ostream&,自定义类&);
从格式上看,>>重载函数和<<重载函数只能定义为友元函数,不能定义为成员函数,因为函数有两个形参,并且第一个形参不是自定义类型。
1.重载流插入运算符“<<”
class Complex
{
private:
double real;
double imag;
public:
Complex() { real = 0; imag = 0; }
Complex(double r, double i) { real = r; imag = i; }
Complex operator + (Complex &c2);
friend ostream& operator << (ostream&, Complex&);
};
Complex Complex::operator + (Complex &c2)
{
return Complex(real + c2.real, imag + c2.imag);
}
ostream& operator << (ostream& output, Complex& c)
{
output << "(" << c.real << "+" << c.imag << "i)" << endl;
return output;
}
int main()
{
Complex c1(2, 4), c2(6, 10), c3;
c3 = c1 + c2;
cout << c3;
return 0;
}
运算符的左边是ostream的对象cout,右边是程序员自定义类complex的对象c3,语句符合运算符重载友元函数operator<<的形参类型要求。
系统调用友元函数,C++把这个语句解释为:operator <<(cout , c3);
通过形参引用传递,函数中的output就是cout,函数中的c就是c3。
函数就变成:{cout<<"("<<c3.real<<"+"<<c3.imag<<"i)"<<endl;return cout;}
2.重载流提取运算符“>>”
class Complex
{
private:
double real;
double imag;
public:
Complex() { real = 0; imag = 0; }
Complex(double r, double i) { real = r; imag = i; }
Complex operator + (Complex &c2);
friend ostream& operator << (ostream&, Complex&);
friend istream& operator >> (istream&, Complex&);
};
Complex Complex::operator + (Complex &c2)
{
return Complex(real + c2.real, imag + c2.imag);
}
ostream& operator << (ostream& output, Complex& c)
{
output << "(" << c.real << "+" << c.imag << "i)" << endl;
return output;
}
istream& operator >> (istream& input, Complex& c)
{
cout << " 请输入复数的实部和虚部:";
input >> c.real >> c.imag;
return input;
}
int main()
{
Complex c1, c2;
cin >> c1 >> c2;
cout << "c1=" << c1 << endl;
cout << "c2=" << c2 << endl;
return 0;
}