一、为什么要重载运算符
通过运算符重载,扩大了C++已有运算符的作用,使运算符能用于类对象;使用运算符重载,能使程序易于编写、阅读和维护;运算符被重载后,其原有的功能仍然保留,没有丧失或改变。
例1 使用成员函数模拟对象加法(非运算符重载)
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(); //声明初始化构造函数(无参)
Complex(double r,double i); //声明初始化构造函数(带参)
Complex object_add(Complex &Complex1);//声明成员函数:用于对象的加法;对象入参,类名&对象名
void display( );
private:
double real;
double imag;
};
Complex::Complex( )
{
real=0;
imag=0;
}
Complex::Complex(double r,double i)
{
real=r;
imag=i;
}
Complex Complex::object_add(Complex &Complex1)//定义时,对象入参,“类名&对象名”形式
{
Complex c;
c.real=real+Complex1.real;
c.imag=imag+Complex1.imag;
return c;
}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(3,4),c2(5,-10),c3,c4;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
c3=c1.object_add(c2); //注:调用成员函数,c2作为入参,实现c1+c2;调用时,仅对象名即可
cout<<"c1+c2=";
c3.display( );
return 0;
}
二、通过运算符重载函数实现运算符的重载
运算符重载实质上是函数的重载!
定义一个重载运算符的函数,需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。
运算符重载函数的一般形式:
函数类型 operator运算符名称 (形参表列)
{ 运算符重载函数体 }
注:1)形参列表里可以不止一个参数;如果是对象的话,需要“类名&对象名”的形式
例2 对算数运算符+的重载,实现对象加法
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(); //声明初始化构造函数(无参)
Complex(double r,double i); //声明初始化构造函数(带参)
Complex operator+(Complex &Complex1); //声明成员函数(运算符重载函数):用于对象的加法;对象入参,类名&对象名
void display( );
private:
double real;
double imag;
};
Complex::Complex() //初始化构造函数(无参),用于定义对象时的初始化,如:Complex c1;数据成员都初始化为0
{
real=0;
imag=0;
}
Complex::Complex(double r,double i) //初始化构造函数(带参数),用于带参定义,如:Complex c1(3,4),即real = 3, imag = 4;
{
real=r;
imag=i;
}
Complex Complex::operator+(Complex &Complex1) // +运算符重载
{
Complex c;
c.real=real+Complex1.real;
c.imag=imag+Complex1.imag;
return c;
}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(3,4),c2(5,-10),c3,c4;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
c3=c1+c2; //+运算符重载后的用法,直接实现对象加法;
cout<<"c1+c2=";
c3.display( );
return 0;
}
三、重载运算符的规则
1、不允许创造新的运算符,只能对已有的C++运算符进行重载。
2、重载不能改变运算符运算量的个数(即操作数),不能改变运算符的优先级,不能改变运算符的结合性。
优先级 | 运算符 | 运算符名称 | 结合性 | 变量个数 | 可重载性 |
1 | :: | 作用域运算符 | 从左向右 | - | 否 |
2 | . -> [] () | 成员访问运算符 成员指向运算符 下标运算符 函数调用运算符 | 从左向右 | 双目 | 仅.否 |
3 | ++ -- ~ ! + - & * (type)/type() sizeof() new delete castname_cast | 自增运算符 自减运算符 按位取反运算符 逻辑非运算符 正号 负号 引用/取地址运算符 指针运算符 强制转换类型运算符 类型长度运算符 内存分配运算符 取消分配内存运算符 类型转换运算符 | 从右向左 | 单目 | 仅sizeof否 |
4 | .* ->* | 成员 指针运算符 | 从左向右 | 双目 | 仅.*否 |
5 | * / % | 乘 除 取余 | 从左向右 | 双目 | 可 |
6 | + - | 加 减 | 从左向右 | 双目 | 可 |
7 | << >> | 按位左移 按位右移 | 从左向右 | 双目 | 可 |
8 | < <= > >= | 小于 小于等于 大于 大于等于 | 从左向右 | 双目 | 可 |
9 | == != | 等于 不等于 | 从左向右 | 双目 | 可 |
10 | & | 按位与 | 从左向右 | 双目 | 可 |
11 | ^ | 按位异或 | 从左向右 | 双目 | 可 |
12 | | | 按位或 | 从左向右 | 双目 | 可 |
13 | && | 逻辑与 | 从左向右 | 双目 | 可 |
14 | || | 逻辑或 逻辑非 | 从左向右 | 双目 | 可 |
15 | ?: | 条件运算符 | 从右向左 | 三目 | 否 |
16 | = += -= *= /= %= <<= >>= &= |= ^= | 赋值运算符 …… 复合赋值运算符 | 从右向左 | 双目 | 可 |
17 | throw | 抛出异常运算符 | 从左向右 | - | 否 |
18 | , | 逗号运算符 | 从左向右 | 双目 | 可 |
3、重载运算符的函数不能带默认的参数,即不能有固定值的入参。
4、重载的运算符必须和用户自定义类型的对象一起使用,参数至少有一个是类对象或其引用。
5、重载运算符的功能应类似该运算符作用于标准类型数据时所实现的功能。
注:重申C++中如下5个运算符不能重载
序号 | 运算符 | 运算符说明 | 备注 |
1 | . | 成员运算符 | 不能重载 |
2 | .* | 成员指针访问运算符 | |
3 | :: | 域运算符 | |
4 | sizeof | 求字节运算符 | |
5 | ?: | 条件运算符 |
四、运算符重载函数的实现方法
(一)成员函数方式实现+运算符重载函数
此方法,通过this指针访问本类数据成员,少写一个函数的参数(因为隐含了this指针),所以其第一个入参必须是当前类型的对象,不能是其他类的对象或其他标准类型(如int)。
例3 成员函数方式重载+算数运算符实现复数+复数(this指针方式)
//成员函数实现:复数+复数(本类对象的相加)
class Complex
{
public:
Complex operator+ (Complex &object); //运算符重载函数的声明
……
}
Complex Complex::operator + (Complex &object) //运算符重载函数的定义,有作用域
{return Complex(real+object.real, imag+object.imag);}
//调用时
Complex object1(1,2), object2(3,4);
Complex object3 = object1 + object2;
隐含的this指针如下:
//定义等价于:
Complex Complex::operator + (Complex *this, Complex &object)
{return Complex(this->real+object.real, this->imag+object.imag);}
完整代码如下:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex();
Complex(double r,double i);
Complex operator+(Complex &object);
void display( );
private:
double real;
double imag;
};
Complex::Complex( ) //初始化构造函数
{
real=0;
imag=0;
}
Complex::Complex(double r,double i) //重载初始化构造函数
{
real=r;
imag=i;
}
Complex Complex::operator+(Complex &object) //+运算符重载函数
{return Complex(real+object.real, imag+object.imag);}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(1,2),c2(3,-4),c3;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
c3=c1 + c2;
cout<<"c1+c2=";
c3.display( );
return 0;
}
例4 成员函数方式重载实现复数加法(两个显示入参)是否可行?
class Complex
{
public:
Complex();
Complex(double r,double i);
Complex operator+(Complex &object1, Complex &object2);//声明
void display( );
private:
double real;
double imag;
};
Complex Complex::operator+(Complex &object1, Complex &object2) //成员函数(运算符重载函数)定义
{return Complex(object1.real+object2.real, object1.imag+object2.imag);}
说明:此处会报错“main.cpp:8:13: error: overloaded 'operator+' must be a unary or binary operator (has 3 parameters)”
因为,成员函数方式下,运算符重载函数默认已经调用了一个(*this)作为入参,再加上此处定义的object1和object2,入参达到了3个,超过了+作为双目运算符的操作数上限(2个)。
例5 成员函数方式实现复数+实数
#include <iostream>
using namespace std;
class Complex
{
public:
Complex();
Complex(double r,double i);
Complex operator+(int &a); //声明+重载函数1(成员函数)复数+实数
Complex operator+(Complex &object); //声明+重载函数2(成员函数)复数+复数
void display( );
private:
double real;
double imag;
};
Complex::Complex( ) //初始化构造函数
{
real=0;
imag=0;
}
Complex::Complex(double r,double i) //重载初始化构造函数
{
real=r;
imag=i;
}
Complex Complex::operator+(int &a) //定义+重载函数1(成员函数)复数+实数
{return Complex(real+a, imag);}
Complex Complex::operator+(Complex &object) //定义+重载函数2(成员函数)复数+复数
{return Complex(real+object.real, imag+object.imag);}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(1,2),c2(-4,-2),c3, c4;
int b = 4;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
cout<<"int b = "<<b<<endl;
cout<<"++++++++++++++++"<<endl;
c3=c1 + c2; //调用Complex operator+(Complex &object)
cout<<"c3 = c1 + c2 = ";
c3.display( );
c4=c2 + b; //调用Complex operator+(int &a)
//c4 = b + c2 因为没有定义实数+复数,所有此写法会报错" invalid operands"无效操作数!
cout<<"c4 = c2 + b = ";
c4.display( );
return 0;
}
注:因为成员函数方式实现运算符重载时,第一个入参必须是当前类的对象,因此无法使用成员函数实现实数+复数的运算重载,但可以使用友元函数重载。
(二)友元函数方式实现+运算符重载函数
友元函数:在本类以外定义的函数,在本类体中用friend进行声明,此函数就称为本类的友元函数,友元函数可以访问这个类中的私有成员。
友元函数的方式,既可以实现同类对象的加法,也可以实现不同类对象的加法(但要注意左操作数和右操作数在定义和调用时要一致)。
例6 友元函数方式重载+运算符实现复数+复数
//友元函数方式实现:复数+复数
class Complex
{
public:
friend Complex operator+ (Complex &object1, Complex &object2); //运算符重载函数的声明
……
}
Complex operator + (Complex &object1, Complex &object2) //运算符重载函数的定义;
{return Complex(object1.real+object2.real, object1.imag+object2.imag);}
//调用时
Complex object1(1,2),object2(3,4);
Complex object3 = object1 + object2;
完整代码如下:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex();
Complex(double r,double i);
friend Complex operator+(Complex &object1, Complex &object2);//声明友元函数
void display( );
private:
double real;
double imag;
};
Complex::Complex( ) //初始化构造函数
{
real=0;
imag=0;
}
Complex::Complex(double r,double i) //重载初始化构造函数
{
real=r;
imag=i;
}
Complex operator+(Complex &object1, Complex &object2) //友元函数(运算符重载函数)定义
{return Complex(object1.real+object2.real, object1.imag+object2.imag);}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(1,2),c2(3,-4),c3;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
c3=c1 + c2;
cout<<"c1+c2=";
c3.display( );
return 0;
}
例7 友元函数方式重载+算数运算符实现实数+复数
//友元函数方式实现:实数+复数
class Complex
{
public:
friend Complex operator+ (int &a,Complex &object); //运算符重载函数的声明
……
}
Complex operator + (int &a,Complex &object) //运算符重载函数的定义;
{return Complex(a+object.real, object.imag);}
完整代码
#include <iostream>
using namespace std;
class Complex
{
public:
Complex();
Complex(double r,double i);
Complex operator+(int &a); //声明+重载函数1(成员函数)复数+实数
Complex operator+(Complex &object); //声明+重载函数2(成员函数)复数+复数
//friend Complex operator+(Complex &object1, Complex &object2); 声明+重载函数3(友元函数)复数+复数
//注:由于已经使用成员函数实现了复数+复数,重复声明或定义会造成调用歧义,导致编译报错
friend Complex operator+(int &a, Complex &object); //声明+重载函数4(友元函数)实数+复数
void display( );
private:
double real;
double imag;
};
Complex::Complex( ) //初始化构造函数
{
real=0;
imag=0;
}
Complex::Complex(double r,double i) //重载初始化构造函数
{
real=r;
imag=i;
}
Complex Complex::operator+(int &a) //定义+重载函数1(成员函数)复数+实数
{return Complex(real+a, imag);}
Complex Complex::operator+(Complex &object) //定义+重载函数2(成员函数)复数+复数
{return Complex(real+object.real, imag+object.imag);}
Complex operator+(int &a, Complex &object) //定义+重载函数4(友元函数)实数+复数
{return Complex(a+object.real, object.imag);}
void Complex::display()
{
cout<<"("<<real<<",";
cout<<imag<<"i)"<<endl;
}
int main( )
{
Complex c1(1,2),c2(-4,-2),c3, c4, c5, c6;
int a = -1;
int b = 4;
cout<<"c1=";
c1.display( );
cout<<"c2=";
c2.display( );
cout<<"int a = "<<a<<endl;
cout<<"int b = "<<b<<endl;
cout<<"++++++++++++++++"<<endl;
c3=c1 + c2; //调用Complex operator+(Complex &object)
cout<<"c3 = c1 + c2 = ";
c3.display( );
c4=c2 + b; //调用Complex operator+(int &a)
cout<<"c4 = c2 + b = ";
c4.display( );
cout<<"----------------"<<endl;
c5 = c2 +c1; //调用Complex operator+(Complex &object)
cout<<"c5 = c2 + c1 = ";
c5.display( );
c6 = a + c1; //调用Complex operator+(int &a, Complex &object)
cout<<"c6 = a + c1 = ";
c6.display( );
return 0;
}
注意:不要重复定义相同功能(同运算符,同操作数)的运算符重载函数,否则调用时会引起歧义,编译报错。
(三)普通函数方式实现运算符重载函数
只有在极少的情况下才使用(因普通函数一般不能直接访问类的私有成员)。
综上:首先选择成员函数方式,当成员函数方式无法实现时,选择友元函数方式实现,最后才使用普通函数形式。
(四)运算符重载的注意事项
1、由于友元的使用会破坏类的封装,原则上要尽量将运算符函数作为成员函数;
2、一般单目运算符重载为成员函数,双目运算符重载为友元函数;
3、对于数学上交换律成立的运算符,且左右两侧操作对象不一致时(如:复数+实数/实数+复数),应两次重载运算符;
4、有的运算符必须定义为类的成员函数:赋值运算符、下标运算符、函数调用运算符;
5、有的运算符则不能定义为类的成员函数:流插入<<和流提取运算“>>、类型转换运算符。
后续一篇将重点就运算符的重载和使用的举例。