C++运算符重载
一、运算符重载:
1.定义:重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符的操作数目相同,使用运算符重载能够提高代码可读性。
运算符函数定义一般格式:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
运算符重载是要遵循规则:
1.除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,其他运算符都可以重载。
2.重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
3.运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
4.重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
5.运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
6.运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似。
7.一个运算符被重载后,原有意思没有失去,只是定义了相对一特定类的一个新运算符。
运算符重载的格式
运算符重载有两种形式,一是重载成员函数,二是重载友元函数(或普通类外)函数形式
重载为成员函数的形式:
class Complex{
int real,image;
public:
Complex(int r=0,int i=0):real(r),image(i){};
Complex operator+(const Complex& c);//成员函数形式重载"+"
};
Complex Complex::operator+(const Complex& c){ //具体实现
return Complex(real+c.real,image+c.image);
}
重载友元函数:
class Complex{
int real,image;
public:
Complex(int r=0,int i=0):real(r),image(i){};
friend Complex operator+(const Complex& c,const Complex& c2);//友元函数形式重载"+"
};
Complex operator+(const Complex& c,const Complex& c2){ //具体实现
return Complex(c.real+c2.real,c.image+c2.image);
}
运算符重载比函数重载多了一个operator关键字,重载为成员函数时有一个参数为类的对象本身,而重载为外部函数(友元或普通函数)时需要将所有参数写出来。
返回类型 operator 运算符(参数列表){
}
或者重载为成员函数形式:
返回类型 所在类的类名::operator 运算符(参数列表){
}
运算符重载限制(规则)
运算符重载方法步骤:
全局函数、类成员函数方法实现运算符重载步骤
1.操作符重载是一个函数,写出函数名称operator+()
2.根据操作数写出函数参数
3.完善函数返回值(函数返回引用、指针、元素)实现函数业务
#include<iostream>
using namespace std;
class Complex{
public:
Complex();
Complex(double real,double imag);
//运算符重载
Complex operator+(const Complex &A) const;
void display() const;
private:
double m_real;
double m_imag;
};
Complex::Complex():m_real(0.0),m_imag(0.0){
};
Complex::Complex(double real,double imag):m_real(real),m_imag(imag){
}
//重载实现
Complex Complex::operator+(const Complex &A) const{
Complex B;
B.m_real=this->m_real+A.m_real;
B.m_imag=this->m_imag+A.m_imag;
return B;
}
void Complex::display() const{
cout<<m_real<<"+"<<m_imag<<"i"<<endl;
}
int main(){
Complex c1(4.3,5.8);
Complex c2(2.4,3.7);
Complex c3;
c3=c1+c2;
c3.display();
cout<<endl;
return 0;
}
运算符重载其实就是定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数。也就是说,运算符重载是通过函数实现的,它本质上是函数重载。
在全局范围内重载运算符:
运算符重载函数不仅可以作为类的成员函数,还可以作为全局函数。更改上面的代码,在全局范围内重载+
,实现复数的加法运算:
#include<iostream>
using namespace std;
class Complex{
public:
Complex();
Complex(double real,double imag);
//运算符重载
friend Complex operator+(const Complex &A const Complex &B);
void display() const;
private:
double m_real;
double m_imag;
};
Complex::Complex():m_real(0.0),m_imag(0.0){
};
Complex::Complex(double real,double imag):m_real(real),m_imag(imag){
}
//重载实现
Complex 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;
}
void Complex::display() const{
cout<<m_real<<"+"<<m_imag<<"i"<<endl;
}
int main(){
Complex c1(4.3,5.8);
Complex c2(2.4,3.7);
Complex c3;
c3=c1+c2;
c3.display();
cout<<endl;
return 0;
}
运算符重载函数不是 complex 类的成员函数,但是却用到了 complex 类的 private 成员变量,所以必须在 complex 类中将该函数声明为友元函数。
赋值运算符:
(1)编译器把运算符合成了,编译器可以识别两个对象,a2=a1把a1的内容拷贝到a2,类似于拷贝构造函数,把一个对象内容原封不动拷贝到另一个对象。但是:若是两个对象公用一块内存空间时,一个空间销毁,会使得另外一个空间成为野指针还存在内存泄漏等。
(2)显式重载赋值运算符:
方式一、使用类的成员函数来写,有一个隐藏this指针,看起来只有一个参数,但实际上有两个参数。不论是返回值还是参数的位置,若是给定一个类类型的对象,最好将其给成引用,引用的方式不用创建新的临时对象。赋值操作符右操作数不会发生改变,所以可以使用const修饰。
class Date
{
public:
Date(int year=290,int month=11,int day=1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int):" << this << endl;
}
Date(const Date &d)
:_year(d._year)
,_month(d._month)
, _day(d._day)
{
cout << "Date(const Date &):" << this << endl;
}
void operator=(const Date&d){
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1(122,3,4);
Date d2;
d2 = d1; //d2.operator=(d1);//Date::operator=(&d2,&d1)d1:通过参数压栈传入,d2通过ecx寄存器传入
return 0;
}
缺陷不能连续赋值:如:d1=d2=d3:d3赋值给d2,d2赋值给d1:d1.operator=(d2.operator=(d3))
方式二、返回左操作数
Date& operator=(const Date&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//d外部实参的引用,this是当前对象,
//this和d的生命周期都比函数的生命周期长,但是我们返回this指针,因为
//需要返回左操作数
}
缺陷:不能自己给自己赋值,如:d1=d1和Date d4=&d1;……d1=d4;
方式三、判断自己给自己赋值,即this与当前的地址是否相同
Date& operator=(const Date&d)
{
if (this!=&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
3、注意
(1)不能通过连接其他符号来创建新的操作符:比如operator@
(2)重载操作符必须有一个类类型或者枚举类型的操作数
构造一个加法的函数位于全局范围(普通的成员函数)内,这个加法函数有几个参数,给出几个参数,没有隐式的this指针)
1.直接返回左操作数加右操作数不成立,需要给定一个自定义类型
class Date
{
public:
Date(int year=290,int month=11,int day=1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int):" << this << endl;
}
Date(const Date &d)
:_year(d._year)
,_month(d._month)
, _day(d._day)
{
cout << "Date(const Date &):" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int operator+(int left,int right){
return left+right;
}
+可以直接处理整形的加号,应该给一个自定义的类型:枚举就是自定义类型;
enum DATA{one,two,three};
int operator+(int left,DATA right){
return left+right;
};
2.用于内置类型的操作符,其含义不能改变,比如:内置的整形+,不能改变其含义
DATA operator+(int days){
Date tmp(*this);
tmp._day-=days;
return tmp;
}
3.作为类成员的重载函数,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参:写成类的成员函数,若是有两个操作数,只需要给一个操作数,若是有一个操作数,就不需要。有一个隐含的this指针。
4.一般将算术操作符定义为非成员函数(给成普通函数、会传两个参数)将赋值运算符定义成员函数
5.操作符定义为非类的成员函数时,一般将其定义为类的友元
6.==和!=操作符一般要成对重载
7.下标操作符[]:一个非const成员并返回引用,一个是const成员并 返回引用
8.解引用操作符*和->操作符,不显示任何参数
9.前置式++/–必须返回被增量或者减量的引用,后缀式操作符必须返 回旧值,并且应该是值返回而不是引用返回++只有一个参数,所以给成成员函数的时候,不需要给定参数。有返回值。返回值的生命周期比函数长,则使用引用。
class DATE{
public:
DATE(int year=100,int month=1,int day=10):_year(year),_month(month),_day(day){
cout<<"DATE(int):"<<this<<endl;
}
DATE(const DATE &d):_year(d._year),_month(d.month),_day(d.day){ cout<<"Data(const Date &):"<<this<<endl;
}
};
//前置++与后置++的类型相同
//同一个作用域里面定义两个作用域和原型都相同的函数,给另外一个函数给一个参数,构成重载
//a=++b;
DATE &operator++(){
_day+=1;
return *this;
}
//a=b++;
DATE operator++(int){
DATE temp(*this);
_day+=1;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
DATE d1(122, 3, 4);
DATE d2;
d2 = d1++;
return 0;
}
后置++成员函数里面只给了一个参数,有两个参数,而实际传参的时候只传了一个参数(编译器已经替我们传递了一个参数),参数只给出了类型,没有给定值。因为后置++给一个参数是为了和前置++形成重载,所以不需要值;这里的这个参数我们不使用,但是必须给出来。
10.输入操作符>>和输出操作符<<必须定义为类的友元函数
输出操作符不能直接输出一个对象,因为对象里面包含了多个成员,不能确定是输出哪一个成员,重载输出运算符,输出运算符可以接连输出,输出运算符有两个操作数。
(1)不能出力"cout<<a1;"可以处理"a3.operator<<(cout);"和"a3<<cout;"这两种情况因为该函数(如下)有两个参数,第一个参数是当前调用对象的地址(this),所以cout不能出现在左边,cout是右操作数。
class DATE{
public:
DATE(int year=2019,int month=10,int day=22):_year(year),_month(month),_day(day){}
void operator<<(ostream *_cout){ //ostream输出流对象
_cout<<_year<<_month<<_day;//输出运算符的重载不用换行,外部用户自己定义
}
private:
int _year;
int _month;
int _day;
};
void TestDATA(){
DATE d1(2018,10,22);
DATE d2(d1);
DATE d3;
d3=d2;
d3.operator<<(cout);
d3<<cout;
}
(2)输出操作符的本意是“cout<<d3;”把d3写入到,再把对象输出到控制台;上面的方式"d3<<cout;"把cout当成了参数(放到了对象之中。不符合常规。修改为:不把输出操作符重载成类的成员函数(全局函数),给成普通的函数(括号之中有几个参数,就有几个参数),但是此程序会产生错误,因为成员变量是私有的。
class DATE
{
public:
DAET(int year = 2019, int month = 10,int day = 22)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream &_cout, const DATE&d){ //此时cout作为左操作数,要打印中的内容作为右操作数
_cout<<d._year<<d._month<<d._day<<endl;
}
void TestDATE(){
DATE d1(2018.9,21);
DATE d2(d1);
DATE d3;
d3=d2;
d3.operator<<(cout);
d3<<cout;
}
类外函数operator访问类内私有成员
1.写一些公有的成员函数,将类中的私有成员获取出来,调用这些公有的接口实现
2.友元函数
class DATE
{
public:
friend ostream& operator<<(ostream& _cout,const DATE &d);
DAET(int year = 2019, int month = 10,int day = 22)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream operator<<(ostream &_cout, const DATE&d){ //此时cout作为左操作数,要打印中的内容作为右操作数
_cout<<d._year<<d._month<<d._day<<endl;
}
void TestDATE(){
DATE d1(2018.9,21);
DATE d2(d1);
DATE d3;
cout<<d3<<endl;
}
(3)不能连着输出,应该有返回值
ostream &operator<<(ostream &_cout, const Date&d)//此时cout作为左操作数,对象中的内容作为右操作数
{
_cout << d._year << "/" << d._month << "/" << d._day;
return _cout;//cout的生命周期比函数长,需要返回引用
}
实现日期函数:加、减、两个日期相减、两个日期比大小(>,<,==,!=),前置++,--,后置++,--(日期需要考虑合法性以及是否为闰年)类中涉及到资源的管理,申请空间……的需要给出这三个函数。
class DATE{
public:
DATE(int year=2019,int month=10,int day=22):_year(year),_month(month),_day(day){}
bool operator>(const DATE &d){
if(_year>d._year||_year==d._year&&_month>d.month||_year==d._year&&_month==d._month&&_day>d.day)
return true;
return false;
}
bool operator==(const Date &d)
{
return _year == d._year&&
_month == d._month&&
_day == d._day;
}
bool operator!=(const Date& d)
{
return !(*this == d);//相等返回真,取反即为假
}
Date& operator++()//前置++:用加完后的新值进行返回
{
_day += 1;
return *this;//this的生命周期比函数长
}
Date operator++(int)
{
Date tmp(*this);
_day += 1;
return *this;
}
private:
int _year;
int _month;
int _day;
};
知道函数的运行时间(精确计算)
函数调用过程可以理解为
(1)返回临时变量
#include<iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test():" << this << endl;
}
Test(const Test& t)
{
cout << "Test(const Test& t):" << this << endl;
}
//赋值运算符的重载
Test& operator=(const Test& t)
{
cout << this << "=" << &t << endl;
if (this != &t)
{
;//赋值操作
}
return *this;
}
~Test()
{
cout << "~Test():" << this << endl;
}
};
Test FunTestc(Test t)
{
Test tt1;
Test tmp(t);
tt1 = tmp;
return tt1;
}
void TestFunc()
{
Test t1;
Test t2;
t2 = FunTestc(t1);
}
int main()
{
TestFunc();
return 0;
}
(2)返回引用
Test &FunTestc(Test &t)
{
Test tt1;
Test tmp(t);
tt1 = tmp;
return t;
}
void TestFunc()
{
Test t1;
Test t2;
t2 = FunTestc(t1);
}