重载的本质是函数重载。他可以赋予运算符多重含义,使运算符作用于不同类型的数据时有不同类型的行为。
运算符重载的具体格式:
返回值类型 operator 运算符 (形参表)
{
......
}
运算符重载为普通函数
在运算符重载时,可以重载为普通成员函数和类成员函数,这一部分讲解一下普通成员函数。
这里需要注意以下几点,在程序编译时:
编译器会把有运算符的地方转换为对运算符函数的调用;
把运算符的操作数->运算函数的参数;
多次重载时会根据实参类型调用具体运算符函数
以加减运算符为例
#include<iostream>
using namespace std;
class complex{//一个复数类,有实数和虚数
public:
double real;
double imaginary;
complex(double i=0.0,double j=0.0)//构造函数
{
real=i;
imaginary=j;
}
friend complex operator+(const complex&a,const complex&b);//复数类的一个友元函数,加运算符的重载函数
};
complex operator+(const complex&a,const complex&b)//对加运算符进行定义,两数的实数进行相加,虚数进行相加
{
return complex(a.real+b.real,a.imaginary+b.imaginary);//“类名(参数表)”就代表一个对象
}
int main()
{
complex a(1,2),b(3,4),c;
c=a+b;
cout<<c.real<<" "<<c.imaginary <<endl;
return 0;
}
运行结果是
4 6
从这段代码我们不难看出运算符重载的意义,对于一个具有实数和虚数的复数类,默认运算符是不允许我们直接将两个对象相加的,进行重载之后就可以通过简洁的对象相加得出值。
重载为类的成员函数
重载为类的成员函数与普通成员不同的一点就是需要在类内进行声明,定义可在类内也可在类外
让我们通过一段代码感受一下区别
#include<iostream>
using namespace std
class complex{
public:
double real;
double imaginary;
complex(double r=0.0,double m=0.0):real(r),imaginary(m){}//构造函数的定义
complex operator+(const complex&);//加运算符的类内声明
complex operator-(const complex&);
};
complex complex::operator+(const complex &operand2){//加运算符的类外定义
return complex(real+operand2.real,imaginary+operand2.imaginary );
}
complex complex::operator-(const complex & operand2){//我们不希望原实参被改变,所以使用const类型,使用引用名做形参可以减少代码占用的内存
return complex(real-operand2.real,imaginary-operand2.imaginary);
}
int main()
{
complex x,y(4.3,8.2),z(3.3,1.1);
x=y+z;
x=y-z;
cout<<x.real<<" "<<x.imaginary<<endl;
return 0;
}
运行结果为
1 7.1
除了类内内外声明的区别,还有一点我们可以注意到:普通成员函数和类成员函数的形参个数并不相同,这是为什么呢,因为类内的成员函数里,是有一个隐藏的this指针的,他指向赋值运算符左侧的对象,所以我们可以得出结论:
运算符重载为普通成员函数,形参个数=运算符目数
重载为类的成员函数,形参个数=运算符目数-1
赋值运算符的重载
赋值运算符重载后即使等号两边的数据类型不同,也可以正常进行重载,具体赋值后会发生什么要看重载函数体的内容。有一点需要注意:
赋值运算符只能重载为成员函数
让我们通过一段代码来做进一步认识
#include<iostream>
using namespace std;
class String{
private:
char *str;
public:
String():str(NULL){}//构造函数
const char*c_str(){return str;}
char *operator=(const char*s);//赋值运算符重载的声明
~String();//析构函数
};
char * String::operator=(const char*s){//字符指针类型的成员函数,返回值是一个地址或者指针
if(str) delete[]str;//如果被赋值对象里面有元素,则删除元素,方便后面赋值
if(s){
str=new char[strlen(s)+1];//如果赋值不为空,则为str开辟一个比字符串长度多1的空间,用来存放字符串和'/0'
strcpy(str,s);
}
else
str=NULL;//赋值为空,则str置空返回
return str;
}
String::~String() {
if(str) delete[]str;
};
int main(){
String s;
s="Good Luck,";
cout<<s.c_str()<<endl;
s="Shenzhou 8!";
cout<<s.c_str()<<endl;
return 0;
}
运行结果
Good Luck,
Shenzhou 8!
这里引入一个深拷贝与浅拷贝的概念。
现在有两个字符串类型的对象s1,s2
Sting s2="this";
s1=s2;
只进行简单的赋值会发什么结果呢,输出s1输出正常,但是底层却变成了这样
两个对象指向了同一个地址,如果改变其中一个对象的成员变量,另一个对象也会跟着改变,这不是我们希望的。
所以需要赋值运算符的重载才能实现深拷贝,使地址内的内容进行赋值:
另外在编写赋值运算符重载函数体时,建议不要定义成无返回值类型,返回值最好为*this,这样在连续赋值时不容易出错
流插入运算符和流提取运算符的重载
在进行c++的学习中,可能会有人好奇cin>>和cout<<究竟是什么,其实<<,>>本没有什么奇特的地方,他们就是位运算符里的左移和右移运算符。而之所以cin>>,cout<<可以进输入输出正是因为进行了重载。cout是在iostream中定义的,ostream类的对象,cin是istream类内的对象。
让我们通过一段代码来进行更深入的认识
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class complex{
double real,imag;
public:
complex(double r=0,double i=0):real(r),imag(i){};//构造函数
friend ostream &operator<<(ostream&os,const complex &c);//左移运算符的重载的声明,运算符重载也视作函数,所以也可以使用friend关键字,使其成为类的友元函数
friend istream &operator>>(istream&is, complex &c);
};
ostream &operator<<(ostream &os,const complex &c)
{
os<<c.real<<"+"<<c.imag<<"i";
return os;
}
istream &operator>>(istream &is,complex &c)
{
string s;
is>>s;
int pos=s.find("+",0);//用于在字符串中查找字符或子串,返回第一个匹配的位置
string sTmp=s.substr(0,pos);//字符截取函数
c.real=atof(sTmp.c_str());//将字串转换成浮点型数
sTmp=s.substr(pos+1,s.length()-pos-2);
c.imag=atof(sTmp.c_str());
return is;
}
int main()
{
complex c;
int n;
cin>>c>>n;
cout<<c<<","<<n;
return 0;
}
输入
11 12
输出
11+1i,12
大家也可以自己复制代码运行输入一下,方便理解
自增自减运算符的重载
因为自增自减运算符有前置后置之分,所以我们通过形参的个数对他们进行区分
1、前置运算符作为一元函数重载
重载为类的成员函数:
T opertor++();
T opertor--();
重载为全局函数:
T opertor++(T);
T opertor--(T);
2、后置运算符作为二元运算符重载
重载为类的成员函数:
T opertor++(int);
T opertor--(int);
重载为全局函数:
T opertor++(T,int);
T opertor--(T,int);
通过一段代码来帮助理解
#include<iostream>
using namespace std;
class CDemo{
private:
int n;
public:
CDemo(int i=0):n(i){}
CDemo operator++();
CDemo operator++(int);
operator int(){return n;}//类型强制转换运算符的重载,int(m)等效于m.int(),类型强制运算符重载不能写返回值类型
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(5);
cout<<(d++)<<",";
cout<<d<<",";
cout<<(++d)<<",";
cout<<d<<endl;
cout<<(d--)<<",";
cout<<d<<",";
cout<<(--d)<<",";
cout<<d<<endl;
return 0;
}
运行结果
5,6,7,7
7,6,5,5
运算符重载的注意事项
1、c++不允许定义新的运算符
2、重载后运算符的含义应该符合日常习惯
3、运算符重载不能改变运算符的优先级
4、以下运算符不能被重载
"." ".*" "::" "?:" sizeof
5、重载运算符(),【】,->,=时,重载函数必须声明为类的成员函数