【运算符重载的概念和原理】
运算符重载的实质是编写以运算符作为名称的函数。运算符函数的格式如下:
返回值类型 operator 运算符(形参表)
{
…
}
包含被重载的运算符的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。运算符可以被对次重载。运算符可以被重载为全局函数,也可以被重载为成员函数。一般来说,倾向于将运算符重载为成员函数,这样能够较好的体现运算符和类的关系。
#include <iostream>
using namespace std;
class Complex
{
public;
double real,imag;
Complex(double r=0.0,double i=0.0):real(r),img(i){}
Complex operrater -(const Complex& c);
};
Complex operator +(const Complex& a,const Complex& b)
{
return Complex(a.real+b.real,a.img+b.img);
}
Complex::Complex operator -(const Complex& c)
{
return Comlpex(real-b.real,img-b.img);
}
int main()
{
Complex a(4,4),b(1,1),c;
c=a+b;
cout<<c.real<<","<<c.img<<endl;
cout<<(a-b).real<<","<<(a-b).img<<endl;
return 0;
}
运算符重载为全局函数时,参数的个数等于运算符的目数(即操作数的个数);运算符重载为成员函数时,参数个数等于运算符的目数减一。
第12行,在C++中,“类名(构造函数实参表)”这种写法表示生产一个临时对象。该临时对象没有名字,生存期就到包含它的语句执行完为止。因此,第12行实际上生成了一个临时的Complex对象作为return语句的返回值,该临时对象被初始化为a,b之和。第16行与第12行类似。
【重载赋值运算符“=”】
赋值运算符“=”要求左右两个操作数的类型是匹配的,或至少是兼容的。有时希望“=”两边的操作数的类型即使不兼容也能够成立,这就需要对“=”进行重载。C++规定,“=”只能重载为成员函数。
要编写一个长度可变的字符串类String,该类有一个char*类型的成员变量,用以指向动态分配的存储空间,该存储空间用来存放以’\0’结尾的字符串。String类可以如下编写:
#include<iostream>
#include<cstring>
using namespace std;
class String{
private:
char* str;
public:
String():str(NULL){}
const char*c_str() const{retrun str;}
String& operator=(const char* s);
~String();
};
String& String operator=(const char* s)
//重载“=”,使得obj="hello"能够成立
{
if(str)
delete[]str;
if(s){
str=new char[strlen(s)+1];
strcpy(str,s);
}
else
str=NULL;
return *this;
}
String::~String()
{
if(str)
delete[]str;
}
int main()
{
String s;
s="Good Luck,";
cout<<s.c_str()<<endl;
//String s2="hello!"; //这条语句如果不注释掉会出错
s="Shenzhou 8!";
cout<<s.c_str()<<endl;
return 0;
}
程序的输出结果是:
Good Luck,
Shenzhou 8!
就上面的程序而言,对operator=函数的返回值类型没有什么特别要求,void也可以。但是在对运算符进行重载时,好的风格是应该尽量保留运算符原本的特性,这样其他人在使用这个运算符时才不容易产生困惑。赋值运算符是可以连用的,这个特性在重载后也应该保持。因此,返回值类型为String&才是风格最好的写法。
【浅拷贝和深拷贝】
String s1,s2;
s1="this";
s2="that";
s2=s1;
该程序执行的是浅拷贝。执行完s2=s1后,s2和s1指向同一个地方,这导致s2原来指向的那片动态分配的内存空间再也不会被释放,变成内存垃圾。此外,s2和s1消亡时都会执行delete[] str,这就使得同一片内存空间被释放两次,会导致严重的内存错误,可能引发程序意外中止。而且,如果执行完s1=s2又执行s1=“some”,则会导致s2也被释放。
深拷贝代码如下:
String& String::operator=(const String& s)
{
if(str==s.str)
return *this;
if(str)
delete[]str;
if(s.str){
str=new char[strlen(s.str)+1];
strcpy(str,s.str);
}
else
str=NULL;
return *this;
}
还应该为String类编写如下复制构造函数,以完成深拷贝
String::String(String &s)
{
if(s.str){
str=new char[strlen(s.str)+1];
strcpy(str,s.str);
}
else
str=NULL;
}
【运算符重载为友元函数】
class Complex
{
double real,img;
piblic:
Complex(double r,double i):real(r),img(i){}
Complex operator +(double r);
friend Complex operator +(double r,const Complex &c);
};
Complex Complex::operator+ (double r)
{//能解释C+5
return Complex(real+r,img);
}
Complex operator+(double r,const Complex& c)
{//能解释5+c
return Complex(c.real+r,c.img);
}
【重载流插入运算符和流提取运算符】
为了使cout<<"Star War"能够成立 ostream类需要将<<进行如下重载:
ostream& ostream::operator<<(const char*s)
{
//输出s的代码
return *this;}
为了使cout<<5能够成立,ostream类还需要将<<进行如下重载:
ostream& ostream::operator <<(int n)
{
//输出n的代码
return *this;}
cout<<“Star War”<<5;可以成立
对>>和<<进行重载实例:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class Complex
{
double real,imag;
public:
Complex(double r=0,double i=0):real(r),img(i){}
friend ostream& operator<<(ostream& os,const Complex &c);
friend istream& operatoe>>(istream& is,Complex &c);
};
ostream& ostream::operator<<(ostream& os,const Complex &c)
{
os<<c.real<<"+"<<c,img<<"i";
return os;
}
isream& isream::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.img=atof(sTmp.c_str());
return is;
}
int main()
{
Complex c;
int n;
cin>>c>>n;
cout<<c<<","<<n;
return 0;
}
因为没有办法修改osteam类和istream类,所以只能将<<和>>重载为全局函数的形式。由于这两个函数需要访问Complex类的私有成员,因此在Complex类定义中将它们声明为友元。
第13行,参数os只能是ostream的引用,而不能是ostream的对象,因为ostream的复制构造函数是私有的,没有办法生成ostream参数对象。ostream<<函数的返回值类型设为ostrean&,并且返回os,就能够实现<<的连续使用。
【重载类型强制转换运算符】
在C++,类型的名字(包括类的名字)本身也是一种运算符,即类型强制转换运算符。类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,“(类型名)对象”,这个对对象进行强制类型转换的表达式就等价于“对象.operator 类型名()”,即变成对运算符函数的调用。
下面程序对double类型强制转换运算符进行了重载
#include<iosteam>
using namespace std;
class Complex
{
double real,imag;
public:
Complex(double r=0,double i=0):real(r),img(i){}
operator double(){return real;}
};
int main()
{
Complex c(1.2,3.4);
cout<<(double)c<<endl;
double n=2+c;
cout<<n;
}
【重载自增、自减运算符】
自增运算符++,自减运算符–都可以被重载,但是它们有前置、后置之分。
C++规定,在重载++或–时,允许写一个增加了无用int类型形参的版本,编译器处理++或–前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。
#include<iostream>
using namespace std;
class CDemo{
private:
int n;
public:
CDemo(int i):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(5);
cout<<(d++)<<",";
cout<<d<<",";
cout<<(++d)<<",";
cout<<d<<endl;
cout<<(d--)<<",";
cout<<d<<",";
cout<<(--d)<<",";
cout<<d<<endl;
return 0;
}
【运算符重载的注意事项】
在C++中进行运算符重载时,有以下问题需要注意。
(1)重载后运算符的含义应该符合原有用法习惯。例如重载“+”运算符,完成的功能就应该类似于做加法,在重载的“+”运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。
(2)C++规定,运算符重载不改变运算符的优先级
(3)以下运算符不能被重载:"." "、” “.*” “::” “?:” sizeof
(4)重载运算符() [] ->或者赋值运算符 =时,只能将它们重载为成员函数,不能重载为全局函数
小结
运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。
运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。
运算符也可以重载为成员函数,此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。
必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。
运算符可以重载为全局函数,然后声明为类的友元。
<< 和>>是在iostream中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的
类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象自动转换为某种类型。
自增、自减运算各有两种重载方式,由于区别前置用法和后置用法。
运算符重载不改变运算符的优先级。重载运算符时,应尽量保留运算符原本的特性。