一、运算符重载的概念
重载运算符的概念
C++中的表达式由运算符和操作数按照规则构成。
运算符重载就是给已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为。
运算符重载的目的时使得C++中的运算符也能够用来操作对象。
表4-1 可重载的运算符
可重载的运算符 | 说明 |
---|---|
双目算数运算符 | +(加),-(减),*(乘),/(除).%(取模) |
关系运算符 | ==(等于),!=(不等于),<(小于),>(大于),<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | +(正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),--(自减) |
位运算符 | |(按位或),&(按位与),~(按位取反),^(按位异或),<<(左移),>>(右移) |
赋值运算符 | =(赋值),+=(加法赋值),-=(减法赋值),*=(乘法赋值),/=(除法赋值),%=(取模赋值),&=(按位与赋值),|=(按位或赋值),^=(按位异或赋值),<<=(左移赋值),>>=(右移赋值) |
空间申请与释放 | new(创建对象),delete(释放对象),new,delete[(释放数组) |
其他运算符 | 0(函数调用),->(成员访问,,(逗号), |
表4-2 不可重载的运算符和符号
可重载的运算符 | 说明 |
---|---|
成员访问运算符 | . |
成员指针访问运算符 | .,-> |
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
有两个运算符,系统提供了默认的重载版本。它们是赋值运算符=和地址运算符&。
对于=,系统默认重载为对象成员变量的复制。对于&,系统默认重载为返回任何类对象的地址。
运算符重载的实质是编写以运算符为名称的函数,使用运算符的表达式就被解释为对重载函数的调用。
函数名由关键字operator和其后要重载的运算符符号构成。
运算符函数的格式
返回值类型 operator 运算符(形参表)
{
函数体
}
运算符可以被重载为全局函数,也可以被重载为类的成员函数。
重载运算符为类的成员函数
程序4-1 为类myComplex重载运算符“+”和“-”。
#include
using namespace std;
class myComplex //复数类
{
private:
double real,imag;
public:
myComplex();
myComplex(double r,double i);
void outCom();
myComplex operator-(const myComplex &c);
friend myComplex operator+(const myComplex &c1,const myComplex &c2);
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
void myComplex::outCom()
{
cout<<"("<real-c.real,this->imag-c.imag);
}
myComplex operator+(const myComplex &c1,const myComplex &c2)
{
return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
c1.outCom();
cout<<"operator+";
c2.outCom();
cout<<"=";
res = c1 + c2;
res.outCom();
cout<
重载运算符为友元函数
例4-1中定义的operator-(double r)实现的是复数的实部减去r并返回结果对象。
#include
using namespace std;
class myComplex //复数类
{
private:
double real,imag;
public:
myComplex();
myComplex(double r,double i);
void outCom();
myComplex operator-(const myComplex &c);
myComplex operator-(double r);
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
void myComplex::outCom()
{
cout<<"("<real-c.real,this->imag-c.imag);
}
myComplex myComplex::operator-(double r)
{
return myComplex(this->real-r,this->imag);
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
c1.outCom();
cout<<"operator-";
c2.outCom();
cout<<"=";
res = c1 - 2; //正确
// res = 2 - c1; //错误,不能实现
res.outCom();
cout<
例4-2 重载为友元函数
#include
using namespace std;
class myComplex //复数类
{
private:
double real,imag;
public:
myComplex();
myComplex(double r,double i);
void outCom();
myComplex operator-(const myComplex &c);
friend myComplex operator+(const myComplex &c1,const myComplex &c2);
friend myComplex operator-(const myComplex &c1,const myComplex &c2);
friend myComplex operator-(const myComplex &c1,double r);
friend myComplex operator-(double r,const myComplex &c1);
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
void myComplex::outCom()
{
cout<<"("<real-c.real,this->imag-c.imag);
}
myComplex operator+(const myComplex &c1,const myComplex &c2)
{
return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}
myComplex operator-(const myComplex &c1,const myComplex &c2)
{
return myComplex(c1.real - c2.real,c1.imag - c2.imag);
}
myComplex operator-(const myComplex &c1,double r)
{
return myComplex(c1.real - r,c1.imag);
}
myComplex operator-(double r,const myComplex &c1)
{
return myComplex(r - c1.real,-c1.imag);
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
res = c1 + c2;
res.outCom();
res = c1 - 5;
res.outCom();
res = 5 - c1;
res.outCom();
return 0;
}
(4,6)(-4,2)(4,-2)
重载运算符的规则
在C++中进行运算符重载时,有以下规则:
- 重载后运算符的含义应该符合原有的用法习惯。
- 运算符重载不能改变运算符原有的语义,包括运算符的优先级和结合性。
- 运算符重载不能改变运算符操作数的个数及语法结构。
- 不能创建新的运算符,即重载运算符不能超出C++语言允许重载的运算符范围。
- 重载运算符
()
[]
->
或者赋值运算符=
时,只能将它们重载为成员函数,不能重载为全局函数。 - 运算符重载不能改变该运算符用于基本数据类型对象的含义。
二、重载赋值运算符
重载赋值运算符
C++中的赋值运算符“=”要求左右两个操作数的类型时匹配的,或至少是赋值兼容的。
C++规定,“=”只能重载为成员函数。
程序4-2 为类myComplex重载赋值运算符
#include
using namespace std;
class myComplex //复数类
{
private:
double real,imag;
public:
myComplex();
myComplex(double r,double i);
~myComplex(){}
myComplex addCom(myComplex c1); //成员函数,调用对象与参数对象c1相加
void outCom();
void outCom(string s);
void changeReal(double r); //成员函数
friend myComplex operator+(const myComplex &c1,const myComplex &c2); //c1+c2
friend myComplex operator+(const myComplex &c1,double r); //c1+r
friend myComplex operator+(double r,const myComplex &c1); //r+c1
friend myComplex operator-(const myComplex &c1,const myComplex &c2); //c1-c2
friend myComplex operator-(const myComplex &c1,double r); //c1-r
friend myComplex operator-(double r,const myComplex &c1); //r-c1
myComplex &operator=(const myComplex &c); //成员函数
myComplex &operator=(double); //成员函数
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
myComplex myComplex::addCom(myComplex c1)
{
return myComplex(this->real + c1.real,this->imag + c1.imag);
}
void myComplex::outCom()
{
cout<<"("<real=r;
}
myComplex operator+(const myComplex &c1,const myComplex &c2) //c1+c2
{
return myComplex(c1.real + c2.real,c1.imag + c2.imag); //返回一个临时对象
}
myComplex operator+(const myComplex &c1,double r) //c1+r
{
return myComplex(c1.real + r,c1.imag);
}
myComplex operator+(double r,const myComplex &c1) //r+c1
{
return myComplex(r+c1.real,c1.imag);
}
myComplex operator-(const myComplex &c1,const myComplex &c2) //c1-c2
{
return myComplex(c1.real - c2.real,c1.imag - c2.imag);
}
myComplex operator-(const myComplex &c1,double r) //c1-r
{
return myComplex(c1.real- r,c1.imag);
}
myComplex operator-(double r,const myComplex &c1) //r-c1
{
return myComplex(r-c1.real,-c1.imag);
}
myComplex &myComplex::operator=(const myComplex &c1)
{
this->real = c1.real;
this->imag = c1.imag;
return *this;
}
myComplex &myComplex::operator = (double r)
{
this->real = r;
this->imag = 0;
return *this;
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
c1.outCom("\t\tc1");
c2.outCom("\t\tc2");
res = c1 + c2;
res.outCom("执行res=c1+c2→\tres");
res = c1.addCom(c2);
res.outCom("执行res=c1.addCom(c2)→\tres");
res = c1 + 5;
res.outCom("执行res=c1 + 5→\tres");
res = 5 + c1;
res.outCom("执行res=5 + c1→\tres");
res = c1;
c1.outCom("\t\tc1");
res.outCom("执行res=c1→\tres");
c1.changeReal(-3);
c1.outCom("执行c1.changeReal(-3)→\tc1");
res.outCom("\t\tres");
res = c1;
res.outCom("执行res=c1→\tres");
res = 7;
res.outCom("执行res=7→\tres");
res = 7+8;
res.outCom("执行res=(7+8)→\tres");
res= c1 = c2;
c1.outCom("\t\tc1");
c2.outCom("\t\tc2");
res.outCom("执行res=c1=c2→\tres");
return 0;
}
c1=(1,2)
c2=(3,4)
执行res=c1+c2→ res=(4,6)
执行res=c1.addCom(c2)→ res=(4,6)
执行res=c1 + 5→ res=(6,2)
执行res=5 + c1→ res=(6,2)
c1=(1,2)
执行res=c1→ res=(1,2)
执行c1.changeReal(-3)→ c1=(-3,2)
res=(1,2)
执行res=c1→ res=(-3,2)
执行res=7→ res=(7,0)
执行res=(7+8)→ res=(15,0)
c1=(3,4)
c2=(3,4)
执行res=c1=c2→ res=(3,4)
关于重载赋值运算符应注意
- 赋值运算符必须重载为成员函数
- 为了保持与通常意义下的赋值运算符的功能相一致,应该让重载的赋值运算符能连续使用。
浅拷贝和深拷贝
同类对象之间可以通过赋值运算符“=”互相赋值。如果没有经过重载,“=”的作用就是将赋值号右侧对象的值一一赋值给左侧的对象。这相当于值的拷贝,称为“浅拷贝”。
程序4-3 浅拷贝的含义
#include
using namespace std;
class pointer
{
public:
int a;
int *p;
pointer()
{
a = 100;
p = new int(10);
}
pointer(const pointer &tempp)
{
if(this != &tempp)
{
a = tempp.a;
p = tempp.p;
}
}
};
int main()
{
pointer p1;
pointer p2(p1); //使用复制构造函数
pointer p3 = p1; //使用复制构造函数
cout<<"\n初始化后,各对象的值及内存地址:"<
重载赋值运算符后,赋值语句的功能是将一个对象中指针成员变量指向的内容复制到另一个对象中指针成员变量指向的地方,这样的拷贝叫“深拷贝”。
程序4-4 深拷贝的实现
#include
using namespace std;
class pointer
{
public:
int a;
int *p;
pointer()
{
a = 100;
p = new int(10);
}
pointer(const pointer &tempp)
{
if(this != &tempp)
{
a = tempp.a;
p = tempp.p;
*p = *tempp.p;
}
}
~pointer()
{
if(p != NULL)delete p;
}
pointer &operator=(const pointer &c)
{
if(this == &c)return *this;
delete this->p;
this->p = new int(*c.p);
return *this;
}
};
int main()
{
pointer p1;
pointer p2(p1); //使用复制构造函数
pointer p3;
p1 = p1;
p3 = p1;
cout<<"\n初始化后,各对象的值及内存地址:"<
三、重载流插入运算符和流提取运算符
在C++中,左移运算符“<<”可以和cout一起用于输出,故常被称为“流插入运算符”。
右移运算符“>>”和cin一起用于输入,称为“流提取运算符”。
流是标准库,用户程序中只能继承不能修改,所以重载函数不能是流类库中的成员,而必须重载为类的友元。
重载流插入运算符的一般格式
ostream &operator<<(ostream & output,类名&对象名)
{
...
return output;
}
重载流提取运算符的一般格式
istream &operator>>(istream & input,类名&对象名)
{
...
return input;
}
程序4-5 流插入运算符和流提取运算符重载为友元
#include
#include
#include
using namespace std;
class myComplex
{
private:
double real,imag;
public:
myComplex():real(0),imag(0){}
myComplex(double r,double i):real(r),imag(i){}
friend ostream &operator<<(ostream &os,const myComplex & c);
friend istream &operator>>(istream &is,myComplex &c);
};
ostream & operator<<(ostream &os,const myComplex &c)
{
if(c.imag >=0)
{
os<>(istream &is,myComplex &c)
{
string s;
is>>s; //将a+bi作为字符串读入,a+bi中间不能有空格
int pos = s.find("+",0); //查找虚部
if(pos == -1)pos = s.find("-",1); //虚部为复数时
string sReal = s.substr(0,pos); //分离出代表实部的字符串
c.real = atof(sReal.c_str()); //atof()能将参数内容转换成浮点数
sReal = s.substr(pos,s.length()-pos-1); //分离出代表虚部的字符串
c.imag = atof(sReal.c_str());
return is;
}
int main()
{
myComplex c,c1;
int n;
cout<<"请输入两个复数([-]a±bi)和一个整数,以空格分隔"<>c>>c1>>n;
cout<
程序4-6 流插入运算符重载为成员函数
#include
#include
#include
using namespace std;
class myComplex
{
private:
double real,imag;
public:
myComplex():real(0),imag(0){}
myComplex(double r,double i):real(r),imag(i){}
ostream &operator<<(ostream &os);
friend istream &operator>>(istream &is,myComplex &c);
};
ostream & myComplex::operator<<(ostream &os)
{
if(this->imag >=0)
{
os<real<<"+"<imag<<"i";
}else
{
os<real<<"-"<<(-this->imag)<<"i";
}
return os;
}
istream & operator >>(istream &is,myComplex &c)
{
string s;
is>>s; //将a+bi作为字符串读入,a+bi中间不能有空格
int pos = s.find("+",0); //查找虚部
if(pos == -1)pos = s.find("-",1); //虚部为复数时
string sReal = s.substr(0,pos); //分离出代表实部的字符串
c.real = atof(sReal.c_str()); //atof()能将参数内容转换成浮点数
sReal = s.substr(pos,s.length()-pos-1); //分离出代表虚部的字符串
c.imag = atof(sReal.c_str());
return is;
}
int main()
{
myComplex c,c1;
int n;
cout<<"请输入两个复数([-]a±bi)和一个整数,以空格分隔"<>c>>c1>>n;
c1<<(c<
*四、重载强制类型转换运算符
在C++中,类型的名字(包括类的名字)本身意识一种运算符,即强制类型转换运算符。
强制类型转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。
程序4-7 重载强制类型转换运算符double
#include
using namespace std;
class myComplex
{
double real,imag;
public:
myComplex(double r=0,double i=0):real(r),imag(i){}
operator double()
{
return real;
}
};
int main()
{
myComplex c(1.2,-3.4);
cout<<(double)c<
五、重载自增、自减运算符
自增运算符“++”和自减运算符“--”都可以被重载,但是它们有前置和后置之分。
运算符重载相当于定义了一个以运算符为名字的函数。以自增运算符为例,可以表示为:
CDemo & CDemo::operator++()
{
...
return *this;
}
这样的函数不能区分前置及后置情况。
C++规定,在重载“++”或“--”时,允许写一个增加了无用int类型形参的版本,编译器处理“++”或“--”前置的表达式时,调用参数个数正常的重载函数;处理后置的表达式时,调用多出一个参数的重载函数。
程序4-8 自增运算符重载为成员函数,自减运算符重载为友元。
#include
using namespace std;
class CDemo
{
private:
int n;
public:
CDemo(int i = 0):n(i){}
CDemo & operator++(); //用于前置形式
CDemo & operator++(int); //用于后置形式
operator int() {return n;}
friend CDemo & operator--(CDemo &);
friend CDemo operator--(CDemo &,int);
};
CDemo & CDemo::operator++() //前置++
{
n++;
return *this;
}
CDemo & CDemo::operator++(int k) //后置++,多一个参数
{
CDemo tmp(*this); //记录修改前的对象
n++;
return tmp; //返回修改前的对象
}
CDemo & operator--(CDemo & d) //前置--
{
d.n--;
return d;
}
CDemo operator--(CDemo & d,int) //后置--
{
CDemo tmp(d);
d.n--;
return tmp;
}
int main()
{
CDemo d(10);
cout<<"(d++)="<<(d++)<<","; //等价于“d.operator++(0);”输出10
cout<<"\nd="<
程序4-9 自增、自减运算符重载为成员函数
#include
using namespace std;
class CDemo
{
private:
int n;
public:
CDemo(int i = 0):n(i){}
CDemo & operator++(); //用于前置形式
CDemo & operator++(int); //用于后置形式
operator int()
{
return n;
}
CDemo & operator--();
CDemo operator--(int);
};
CDemo & CDemo::operator++() //前置++
{
n++;
return *this;
}
CDemo & CDemo::operator++(int k) //后置++,多一个参数
{
CDemo tmp(*this); //记录修改前的对象
n++;
return tmp; //返回修改前的对象
}
CDemo & CDemo::operator--() //前置--
{
n--;
return *this;
}
CDemo CDemo::operator--(int k) //后置--
{
CDemo tmp(*this);
n--;
return tmp;
}
int main()
{
CDemo d(10);
cout<<"(d++)="<<(d++)<<","; //等价于“d.operator++(0);”输出10
cout<<"\nd="<