运算符重载
一、运算符重载三种方式
-
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,简化操作 让已有的运算符 适应不同的数据类型。
-
运算符重载其实就是定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数。也就是说,运算符重载是通过函数实现的,它本质上是函数重载。
-
语法:函数的名字由关键字operator及其紧跟的运算符组成 ,比如:
重载+运算符 ==>operator+ 重载=号运算 ==>operator=
-
注意:重载运算符 不要更改 运算符的本质操作(+是数据的相加 不要重载成相减)。
1.重载的限制
多数C++运算符都可以重载,重载的运算符不必是成员函数,但必须至少有一个操作数是用户定义的类型。
-
重载后的运算符必须至少有 一 个操作数是用户定义的类型,防止用户为标准类型重载运算符。如:不能将
减法运算符(-)
重载为计算两个double
值的和,而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。 -
使用运算符时不能违反运算符原来的句法规则。例如,不能将
求模运算符(%)
重载成使用一个操作数:int x;Time shiva;%x;%shiva;
,且不能修改运算符的优先级。 -
不能创建新运算符。例如,不能定义
operator **()
函数来表示求幂。 -
不能重载下面的运算符。
下表大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数重载。 -
=
:赋值运算符; -
()
:函数调用运算符; -
[]
:下标运算符; -
->
:通过指针访问类成员的运算符;
二、重载三种形式
#include <iostream>
#include <cmath>
using namespace std;
class Complex{
public:
//构造函数
Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:
//以全局函数的形式重载
friend Complex operator+(const Complex &c1, const Complex &c2);
//成员函数重载
Complex & operator+=(const Complex &c);
public: //成员函数
double real() const{ return m_real; }
double imag() const{ return m_imag; }
private:
double m_real;
double m_imag;
};
//重载+运算符
Complex operator+(const Complex &c1, const Complex &c2){
Complex c;
c.m_real = c1.m_real + c2.m_real;
c.m_imag = c1.m_imag + c2.m_imag;
return c;
}
//重载+=运算符
Complex & Complex::operator+=(const Complex &c){
this->m_real += c.m_real;
this->m_imag += c.m_imag;
return *this;
}
int main(){
Complex c1(20, 30);
Complex c2(10, 20);
Complex c3(36, 9);
Complex c7 = c1 + c2;
cout<<"c7 = "<<c7.real()<<" + "<<c7.imag()<<"i"<<endl;
c3 += c1;
cout<<"c3 = "<<c3.real()<<" + "<<c3.imag()<<"i"<<endl;
return 0;
}
注:
- 声明为友元函数的好处:
- 和普通函数重载相比,它能够访问非公有成员。
- 将双目运算符重载为友元函数,这样就可以使用交换律。弊端: 友元可以像类成员一样访问类的成员和函数,但是使用不慎会造成破坏类的封装性。
- 作为成员函数还是非成员函数重载运算符(友元函数),对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。
- 非成员函数应是友元函数,这样就可以访问类的私有数据。
Complex类的+
运算符在Complex类声明中原型如下:
friend Cmplex operator+(const Complex &c1,const Complex &c2);
//这个类也可以使用下面的原型:
Complex operator+(const Complex &c)const;
-
加法运算符需要两个操作数,对于成员函数版本来说,一个操作数通过this指针隐式地传递。另一个操作数作为函数参数显示地传递;
-
对于友元版本来说,两个操作数都通过参数来传递。
-
非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数目相同。而非成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象。
三、重载>>和<<(输入和输出运算符)和[](下标运算符)
cout 是 ostream
类的对象,cin 是 istream
类的对象,要想达到这个目标,就必须以全局函数(友元函数)的形式重载<<和>>
,否则就要修改标准库中的类。
1.重载输入运算符<<和>>
①、重载输入运算符<<
istream& operator >>(istream& in, Complex& a)
{
in>> a.m_real >> a.m_imag;
return in;
}
//用全局函数方式重载>>读入两个double类型数据
//istream:输入流
//cin:istream类的对象
//之所以返回istream类对象的引用,是为了能够连续运算
Complex c1,c2;
cin>>c1>>c2;
//不返回,只能一个一个读取:
Complex c1,c2;
cin>>c1;
cin>>c2;
//输入运算符>>使用:
Complex c;
cin>>c;
//cin>>c可以理解为:
operator<<(cin,c);
②、重载输出运算符<<
ostream& operator <<(ostream& out, Complex& a)
{
out << a.m_real << "+" << a.m_imag << "i";
return out;
}
//ostream:输出流
//cout:ostream类的对象
//用引用的方式进行参数传递,并返回对象的引用。能够进行连续输出。
cout<<c3;
//理解为:
operator<<(cout,c3);
代码演示:
#include <iostream>
#include <cmath>
using namespace std;
class Complex
{
private://私有变量 重载时必须加上friend
double m_real;
double m_imag;
public:
Complex(double real = 0.0, double imag = 0.0) :m_real(real), m_imag(imag)
{};
public:
friend Complex operator +(const Complex& a, const Complex& b);
friend istream& operator >>(istream& pt, Complex& a);
friend ostream& operator <<(ostream& out, Complex& a);
};
Complex operator +(const Complex& a, const Complex& b)
{
Complex c;
c.m_real = a.m_real + b.m_real;
c.m_imag = a.m_imag + b.m_imag;
return c;
}
istream& operator >>(istream& in, Complex& a)
{
in >> a.m_real >> a.m_imag;
return in;
}
ostream& operator <<(ostream& out, Complex& a)
{
out << a.m_real << "+" << a.m_imag << "i";
return out;
}
int main()
{
Complex c1, c2, c3;
cin >> c1 >> c2;
c3 = c1 + c2;
cout << "c3=" << c3 << endl;
return 0;
}
2.重载[](下标运算符)
-
c++规定,下标运算符[]只能以成员函数的形式进行重载。
-
格式如下:
返回类型 & operator[](参数)
;可以访问元素和修改元素。或const 返回类型 & operator[] (参数)const
;只能访问元素 -
建议使用时同时提供这两种形式,为了适应const对象。如果不提供第二种,无法访问const对象的任何元素。
#include <iostream>
using namespace std;
class Data
{
private:
int m_length;
int *m_p;
public:
Data(int length=0);
~Data();
int & operator [](int i);
const int &operator [](int i)const;
int length()const{return m_length;}
void display()const;
};
Data::Data(int length):m_length(length)
{
if(length==0)
m_p=NULL;
else
m_p=new int[length];
}
Data::~Data()
{
delete [] m_p;
}
int & Data::operator [](int i)
{
return m_p[i];
}
const int &Data::operator [](int i)const
{
return m_p[i];
}
void Data::display()const
{
for(int i=0;i<m_length;i++)
{
if(i==m_length-1)
cout<<m_p[i]<<endl;
else
cout<<m_p[i]<<", ";
}
}
int main()
{
int n;
cin>>n;
Data ob(n);
for(int i=0,len=ob.length();i<len;i++)
{
ob[i]=i*5;
}
ob.display();
const Data pt(n);
cout<<pt[n-1]<<endl;
return 0;
}
- pt是const对象,如果Data类没有提供const版本的
operator[]
,cout<<pt[n-1]<<endl;
将报错。 - 虽然只是读取数据,没有修改对象。但调用了非const版本的operator[],编译器认为只要调用非const的成员函数,编译器认为会修改对象。
3.重载++
①、重载前后 ++
- 前置++重载时没有参数,而后置++重载时有参数。参数为0,但是在函数体中是用不到的,前缀++效率更高。
- 前置++需要返回引用,因为重载自加运算符后可以返回对象的引用, 以方便在表达式中连续使用。而后置++返回的不是引用,所以不能进行连续使用。
- 编译器看到++a(前置++),它就调用
operator++(a)
,当编译器看到a++(后置++),它就 会去调用operator++(a,int)
。
#include <iostream>
using namespace std;
class Time
{
private:
int hours;
int minutes;
public:
Time()
{
hours = 0;
minutes = 0;
}
Time(int h, int m)
{
hours = h;
minutes = m;
}
void display()
{
cout << "H=" << hours << "M=" << minutes << endl;
}
Time& operator ++()//前缀++,前置递增就是增加当前对象的值,并且返回当前对象
{
++minutes;
if (minutes >= 60)
{
++hours;
minutes -= 60;
}
return *this;
}
Time operator ++(int)//后缀++
{
Time T= *this;
++minutes;
if (minutes >= 60)
{
++hours;
minutes -= 60;
}
return T;
}
};
int main()
{
Time T1(11, 59), T2(10, 40);
++T1;
T1.display();
++T1;
T1.display();
T2++;
T2.display();
T2++;
T2.display();
return 0;
4.重载 前后 - -
实例:
#include <iostream>
using namespace std;
class Data
{
private:
int a;
public:
Data()//默认构造函数
{
}
Data(int a)
{
this->a = a;
}
void display()
{
cout << "a=" << a << endl;
}
Data operator --()//前缀--
{
Data temp;
temp.a = --a;
return temp;
}
Data operator --(int)//后缀--
{
Data ob;
ob.a = a--;
return ob;
}
};
int main()
{
Data D1(11), D2(10);
--D1;
D1.display();
--D1;
D1.display();
D2--;
D2.display();
D2--;
D2.display();
return 0;
}
注:
5.重载()
函数调用运算符 ()
可以被重载用于类的对象,被称为仿函数。当重载 () 时,不是创造了一种新的调用函数的方式,而是创建一个可以传递任意数目参数的运算符函数。
#include<iostream>
using namespace std;
class Distance
{
private:
int feet;
int inches;
public:
Distance()
{
feet=0;
inches=0;
}
Distance(int f,int i):feet(f),inches(i)
{}
Distance operator ()(int a,int b,int c)
{
Distance ob;
ob.feet=a+c+20;
ob.inches=b+c+200;
return ob;
}
void showDistance()
{
cout<<"F="<<feet<<"I="<<inches<<endl;
}
};
int main()
{
Distance D1(12,20),D2;
cout<<"第一个是:";
D1.showDistance();
D2=D1(10,20,30);
cout<<"第二个是:";
D2.showDistance();
return 0;
}
6.重载=运算符(重要)
前提①:
类中 没有指针成员 不需要重载=运算符(默认的浅拷贝就可以 完成)。
实例:
#include<iostream>
using namespace std;
class Person
{
private:
int a;
int b;
public:
Person()
{
a=0;
b=0;
}
Person(int a,int b):a(a),b(b)
{}
void showPerson()
{
cout<<"a="<<a<<",b="<<b<<endl;
}
~Person()
{
}
};
int main()
{
Person ob1(10,20);
ob1.showPerson();
//注意 旧对象 给新对象赋值 调用的是拷贝构造(默认拷贝构造就是单纯的赋值)
Person ob2=ob1;//不是调用赋=运算符
ob2.showPerson();
Person ob3;
ob3=ob1;//调用赋值=运算符(默认赋值=运算是浅拷贝)
ob3.showPerson();
}
前提②:类中有指针成员 必须重载=运算符
指针作为类的成员:
- 拷贝构造函数 必须自定义(默认拷贝构造 是浅拷贝)。
- 必须重载=运算符 (默认=号运算符 是浅拷贝)。
实例:
#include<iostream>
#include<string.h>
using namespace std;
class Person
{
private:
char *name;//指针成员变量
public:
Person()
{
name=NULL;
}
Person(char *name)
{
//根据实际传入的参数,给this->name申请空间
this->name=new char[strlen(name)+1];
//将name指向的,拷贝到this->name指向的空间中
strcpy(this->name,name);
}
Person(const Person &ob)//代表旧对象
{
//this代表新对象
this->name=new char[strlen(ob.name)+1];
strcpy(this->name,name);
}
~Person()
{
if(this->name !=NULL)
delete [] this->name;
this->name=NULL;
}
void showPerson()
{
cout<<"name="<<name<<endl;
}
//成员函数 重载=运算符
Person & operator =(Person &ob)//ob==ob1
{
if(this->name !=NULL)//表明this->name以前有指向
{
delete [] this->name;
this->name=NULL;
//申请空间
this->name=new char[strlen(ob.name)+1];
strcpy(this->name,ob.name);
return *this;
}
}
};
int main()
{
Person ob1("lucy");
ob1.showPerson();
Person ob2=ob1;//调用拷贝构造
Person ob3("bob");
//不重载=默认是浅拷贝
ob3=ob1;
ob3.showPerson();
}
注: