第4章 运算符重载

【运算符重载的概念和原理】
运算符重载的实质是编写以运算符作为名称的函数。运算符函数的格式如下:
返回值类型 operator 运算符(形参表)
{

}
包含被重载的运算符的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。运算符可以被对次重载。运算符可以被重载为全局函数,也可以被重载为成员函数。一般来说,倾向于将运算符重载为成员函数,这样能够较好的体现运算符和类的关系。

#include <iostream>
using namespace std;
class Complex
{
publicdouble 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中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的
类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象自动转换为某种类型。
自增、自减运算各有两种重载方式,由于区别前置用法和后置用法。
运算符重载不改变运算符的优先级。重载运算符时,应尽量保留运算符原本的特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值