运算符重载实质上就是函数重载,重载为成员函数,他就可以自由访问本类的数据成员。实际使用时,总是通过该类的某个对象来访问重载的运算符。
如果是双目运算符,左操作数是对象本身的数据,由this指针指出,右操作数则需要通过运算符重载函数的参数表来传递;如果是单目运算符,操作数由对象的this指针给出,就不再需要任何参数。
1.双目运算符的重载
对于双目运算符B,如果要重载为类的成员函数,使之能够实现表达式a B c
,其中a1是A类的对象,则应当把B重载为A类的成员函数,该函数只有一个形参,形参类型为c所属的类型。经过重载之后,表达式a B c
就相当于调用a.operator B(c)
。
2.单目运算符的重载
对于前置单目运算符U,如“-”(负号)等,如果要重载为类的成员函数,用来实现表达式U a
,其中a为A类对象,那么U应当重载为A类的成员函数,函数没有形参。经过重载之后,表达式U a
就相当于调用a.operator U()
对于后置运算符“++”和“–”来说,如果将它们重载为类的成员函数,用来实现表达式a++
或者a--
,其中a为A类对象,那么运算符就应当重载为A类的成员函数,这时函数要带有一个整型(int)形参。重载之后,表达式a++
和a--
就相当于调用a.operator++(0)
和a.operator--(0)
。这里的int类型的参数在运算中不起任何作用,只是用于区别后置++、–和前置++、–。
(1)两个对象加减乘除的实现原理
【例1】两个对象相加的实现体,即两个对象相加的原理。就是把两个对象中的数据成员依次进行相加。相减同理。
class A
{
public:
A(int i = 0, int j = 0, int k = 0) :m_i(i), m_j(j), m_k(k) {
}
int GetI()const
{
return m_i;
}
int GetJ()const
{
return m_j;
}
void Add(A& s)
{
cout <<"("<< this->m_i + s.m_i << "," << this->m_j + s.m_j << "," << this->m_k + s.m_k<<")"<<endl;
}
private:
int m_i;
int m_j;
int m_k;
};
void main()
{
A a(2, 5, 7);
A b(5, 7, 9);
a.Add(b); //a+b
}
运行结果:
【例2】两个对象相乘的实现体,即两个对象相乘的原理。就是把两个对象中的数据成员依次进行相乘。相除同理。
class A
{
public:
A(int i = 0, int j = 0, int k = 0) :m_i(i), m_j(j), m_k(k) {
}
int GetI()const
{
return m_i;
}
int GetJ()const
{
return m_j;
}
void fn(A& s)
{
cout << "(" << this->m_i * s.m_i << "," << this->m_j * s.m_j << "," << this->m_k * s.m_k << ")" << endl;
}
private:
int m_i;
int m_j;
int m_k;
};
void main()
{
A a(2, 5, 7);
A b(5, 7, 9);
a.fn(b); //a * b
}
运行结果:
以上两个个例子中,在执行Add函数a.Add(b);
或者fn函数a.fn(b);
之前,在执行结果还没有出来的情况下,我们是不知道这两个函数到底是干什么的,这导致程序的可读性不好。虽然上面两个例子中通过函数的调用实现了我们所需要实现的功能,但是这样的写法不如直接写成运算符可以直观的表达出我们要现实的功能是什么。所以引出运算符重载。
运算符重载的原因:
①想让当前的对象像基本数据类型去操作。
②把对象的运算写成运算符的形式比写成函数调用的形式可读性更强。
(2)运算符“+”重载为成员函数
【例3】把运算符“+”重载为成员函数
class A
{
public:
A(int i = 0) :m_i(i) {
}
A operator+(A& s)
{
cout << "调用函数operator +()" << endl;
return this->m_i + s.m_i;//把A类中的m_i和B类中的m_i相加,this指针指向当前对象a中的数据成员m_i的地址
//此处返回值为int型,是因为构造函数可以进行类型转换
}
void print()
{
cout << m_i << endl;
}
private:
int m_i;
};
int main()
{
A a(5);
A b(6);
//a + b;//相当于a.operator+(b) //+(a.b)
(a + b).print();/*遇到+,先看类中有没有重载加法运算符,如果重载了,去调用这个+的重载函数,
把第一个操作数a当作this指针传给这个重载函数,把右操作数b当作实参传给+的重载函数的形参*/
A c;
c = a.operator+(b);
c.print();
return 0;
}
运行结果:
结果分析:
在程序中,遇到“+”,先看此处的“+”是不是加法运算符重载,如果重载了,去调用这个“+”的重载函数,在上述程序,主函数中遇到的a+b
,这里的“+”是加法运算符重载,所以去调用A operator+(A& s)
重载函数,把第一个操作数a当作this指针传给这个重载函数,把右操作数b当作实参传给这个重载函数的形参A& s
。在重载函数A operator+(A& s)
的函数体内将A类对象a中的m_i和B类对象b中的m_i相加的值返回给A类的构造函数的形参,然后通过初始化列表给A类的数据成员m_i进行赋值为A类中对象m_i的值和B类对象m_i的值相加的结果。然后用这个相加后的对象去调用print函数,输出相加的结果。
因为运算符重载的实质为函数重载,所以可以把a+b;
写为a.operator+(b);
这两种写法完全相同。
【例4】复数加减法运算重载为成员函数
class Complex//复数Complex类的定义
{
public://外部接口
Complex(double r = 0.0, double i = 0.0) :real(r), imag(i){
}//构造函数
Complex operator+(const Complex& c2)const;//运算符+重载成员函数
Complex operator-(const Complex& c2)const;//运算符-重载成员函数
void display() const;//输出复数
private://私有成员
double real;//复数实部
double imag;//复数虚部
};
Complex Complex::operator+(const Complex& c2)const//重载运算符+函数实现
{
return Complex(real + c2.real, imag + c2.imag);//创建一个临时的无名对象作为返回值
}
Complex Complex::operator-(const Complex& c2)const//重载运算符-函数实现
{
return Complex(real - c2.real, imag - c2.imag);//创建一个临时的无名对象作为返回值
}
void Complex::display() const
{
cout << "(" << real << "," << imag << ")" << endl;
}
int main()
{
Complex c1(5, 4);//定义复数类对象
Complex c2(2, 10);//定义复数类对象
Complex c3;//定义复数类对象
cout << "c1=";
c1.display();
cout << "c2=";
c2.display();
c3 = c1 + c2;//使用重载运算符+完成复数加法
cout << "c3=c1+c2=";
c3.display();
Complex c4;//定义复数类对象
c4=c1.operator-(c2);//调用重运算符-的存在函数完成复数减法
cout << "c4=c1-c2=";
c4.display();
return 0;
}
运行结果:
分析:
在上例中,将复数的加减运算符重载为复数类的成员函数,可以看出,除了在函数声明及实现时使用了关键字operator以外,运算符重载成员函数与类的普通成员函数没什么区别。在使用的时候,可以直接通过运算符、操作数的方式来完成函数调用。这时运算符“+”、“-”原有的功能都不变,对整型数、浮点数等基本数据类型的运算仍然遵循C++预定义的规则,同时添加了新的针对复数加减运算的功能。“+”这个运算符,作用于不同的对象就会导致完全不同的操作行为,具有了更广泛的多态特征。
上例中重载的“+”、“-”成员函数中,都是创建一个无名的临时对象作为返回值,例如return Complex(real + c2.real, imag + c2.imag);
,这时临时对象语法,它的含义是:调用Complex构造函数创建一个临时对象并返回它。
【例5】对原有运算符“+”进行适当的改造
class A
{
public:
A(int i = 0) :m_i(i) {
}
A& operator+(A& b)
{
m_i = m_i + b.m_i; //this = &a this->m_i = this->m_i + b.m_i a修改了
return *this;
}
void print()
{
cout << m_i << endl;
}
private:
int m_i;
};
void main()
{
A a(5), b(8);
A c(2);
a + b = c;//表达式要放在=的左边,必须有确定的内存单元才可以
a.print();
}
运行结果:
分析:
上述代码中,主程序中,想要把c的值赋给a+b,也就是把a+b作为左值,但是因为a+b没有确定的内存空间,所以a+b不可以作为左值,也就是不可以把c赋值给不确定的内存空间。所以我们要修改运算符“+”重载成员函数,将这个运算符重载函数的返回类型修改为引用类型,函数体内容修改如下:
A& operator+(A& b)
{
m_i = m_i + b.m_i;
return *this;
}
函数返回类型修改为引用类型之后,函数体中的m_i = m_i + b.m_i;
就相当于this->m_i = this->m_i + b.m_i;
。this指针指向对象a,它存放的是a的地址,即this = &a, 相当于把对象a的m_i加对象b的m_i的值赋给了a的m_i,也就是说这条语句实际上把对象a中数据成员的值修改了,把a+b的值存放到了a的内存单元中。此时a+b有了确定的内存空间,即a的内存空间。所以a+b可以作为左值,将c的值赋值给a+b,c赋值给a+b就相当于把c的值放到了a+b的内存单元中,就是把c放到了a的内存单元。输出a.print();
结果之所以是对象c的值,是因为a+b=c;
先把a+b的值赋给a,再把c的值赋给a,重载函数中的return *this;
返回的是已经被修改的最终的a的值,即c的值。
一般情况不这样写加法运算符“+”的重载函数,因为违反了运算符的规则。
(3)默认赋值运算符重载函数
【例6】默认赋值运算符重载函数
class A
{
public:
A(int i = 0) :m_i(i) {
}
void print()
{
cout << m_i << endl;
}
private:
int m_i;
};
int main()
{
A a(5);
A b(6);
(a = b).print();
return 0;
}
运行结果:
结果分析:
我们从程序中可以看出当前运算符“=”并没有重载,但是结果却没错,说明在类中给了一个默认的赋值运算符重载函数。默认的赋值运算符重载函数就是把当前的右边对象的数据成员的值依次赋值给左边对象的数据成员。这里的默认赋值重载函数是浅赋值。如果把这个默认赋值运算符重载函数写出来,就是这样:
A& operator=(const A& s)
{
if(this==&s)//判断自赋值
return *this;
m_i=s.m_i;
return *this;
}
所以 (a = b).print();
相当于是a.operator=(b)
,把a当作this指针传给这个重载函数,把b当作实参传给这个重载函数的形参。上面这个默认的赋值运算符重载函数的返回类型为引用类型,是因为如果使用值类型返回就会产生拷贝构造函数,比较麻烦,而这里由当前运算符组成的表达式可以放在=的左边,它具有相应的内存空间,所以使用引用作为默认的赋值运算符重载函数的返回类型。所以,最后的 return *this;
返回的就是当前对象a的数据成员m_i的值。
(4)单目运算符“++”重载为成员函数。
【例7】单目运算符“++”重载为成员函数。
class A
{
public:
A(int i = 0) :m_i(i) {
}
void print()
{
cout << m_i << endl;
}
A operator++(int) //为了实现++的重载,语法规定,在后++的时候,参数里面写int,没有实际的意义,不用传参
{
cout << "调用函数operator++" << endl;
return m_i++;
}
A& operator++()
{
cout << "调用函数++operator" << endl;
m_i = m_i + 1;
return *this;
}
private:
int m_i;
};
void main()
{
A a(6);
A b(3);
A c(20);
//后置++
cout << "(a++)表达式的值:"<<endl;
(a++).print();//相当于a.++() //a++表达式的值是a没加以前的值6 a的值是a加过1之后的值7,最终返回的是表达式的值
//(a++)这个表达式在执行过程中有两个结果
cout << "a的值:" << endl;
a.print();
//前置++
cout << "(++b)表达式的值:" << endl;
(++b).print(); //b.++() ++b表达式的值是b加过1之后的值,b的值也是b加过1之后的值
//因为++b表达式的值和b的值是相同的,所以没有必要给++b这个表达式开辟空间,直接从b的内存单元中将这个值取出来,所以返回类型可以不用值类型,而是使用引用的返回类型
cout << "b的值:" << endl;
b.print();
}
运行结果:
结果分析:
①语法规定,在后置++的时候,参数里面写int,没有实际的意义,不用传参,它的目的是为了与前置++构成函数重载。
②后置++:(a++)表达式的值是a没加以前的值,a的值变为了a+1以后的值,所以后置++表达式有两个不同的值,但是最终返回的是表达式的值,因此,对于将后置++运算符重载为成员函数时的返回类型为值类型。
③前置++ :(++b)表达式的值和b的值一样,都是b+1以后的值,所以前置++表达式的值只有一个,因为++b表达式的值和b的值是相同的,所以没有必要给++b这个表达式开辟空间,直接从b的内存单元中将这个值取出来,所以返回类型可以不用值类型,而是使用引用的返回类型。
【例8】使用时钟类的例子,单目运算符前置++和后置++的操作数是时钟类的对象,把这些运算符重载为时钟类的成员函数。对于前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数有一个int型形参。
class Clock
{
public:
Clock(int hour, int minute, int second);
void showTime()const;
Clock& operator++();
Clock operator++(int);
private:
int h, m, s;
};
Clock::Clock(int hour, int minute, int second)
{
if (hour >= 0 && minute >= 0 && second >= 0 && hour < 24 && minute < 60 && second < 60)
{
h = hour;
m = minute;
s = second;
}
else
{
cout << "时间不存在" << endl;
}
}
void Clock::showTime()const
{
cout << h << ":" << m << ":" << s << endl;
}
Clock& Clock:: operator++()
{
s++;
if (s >= 60)
{
s -= 60;
m++;
if (m >= 60)
{
m -= 60;
h = (h + 1) % 24;
}
}
return *this;
}
Clock Clock::operator++(int)
{
Clock old = *this;
return *this;
}
int main()
{
Clock c1(24, 59, 1);
Clock c2(10,59,59);
cout << "输出c2的时间:";
c2.showTime();
cout << "输出c2++后的时间";
(c2++).showTime();
cout << "输出++c2后的时间";
(++c2).showTime();
return 0;
}
运行结果: