目录
一、原理和机制
C++中运算符只支持基本数据类型运算,如果需要运算符支持类类型的运算,需要使用C++提供的语法:运算符的重载。
函数原型:返回值 operator运算符(参数列表)
①运算符重载函数也具有自己的返回值类型,函数名字以及参数列表。其返回值类型和参数列表与普通函数类似。
②运算符重载函数名为:关键字operator后面接需要重载的操作符符号。
运算符的重载本质上是通过函数来实现的,将类类型数据的运算过程写成一个特殊的函数,当该类型的对象遇到这种运算时自动调用该函数来完成运算过程。
实现运算符重载的函数既可以是成员函数,也可以是全局函数。
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其目的就是让自定义类型可以像内置类型一样可以直接使用运算符进行操作。
d1 == d2;// 可读性高(书写简单)
IsSame(d1, d2);// 可读性差(书写麻烦)
二、双目运算符的重载
①通过成员函数重载(以加法为例)
成员函数加法的重载:
本类类型 operator+(第二操作数)
{
...
}
双目运算符需要传入两个操作数,第一操作数就是调用该函数的对象,而第二个操作数则以参数列表的形式传入。
②通过全局函数重载(以加法为例)
全局函数加法的重载:
操作数的类型 operator+(第一操作数,第二操作数)
{
...
}
由于这里是在类外使用全局函数访问类中的成员,可能类外重载无法访问类中的成员,可以使用以下方法
1.直接将成员设置为公有(不推荐)
2.使用友元,将全局函数设置为友元函数(推荐)
3.为私有成员提供访问接口
构造一个类表示分数,实现类分数的基本运算
//分数
class Fraction{
public:
Fraction(int x=0,int y=1):x(x),y(y){}
//打印
void print()
{
cout<<this->x<<"/"<<this->y<<endl;
}
//成员函数重载+运算符
Fraction operator+(const Fraction &fb)
{
return Fraction(this->x*fb.y+fb.x*this->y/*分子*/,this->y*fb.y/*分母*/);
}
Fraction operator-(const Fraction &fb)
{
return Fraction(this->x*fb.y-fb.x*this->y/*分子*/,this->y*fb.y/*分母*/);
}
//== > +=
bool operator==(const Fraction &fb)
{
return this->x*fb.y == this->y*fb.x;
}
bool operator>(const Fraction &fb)
{
return this->x*fb.y > this->y*fb.x;
}
Fraction& operator+=(const Fraction &fb)
{
this->x = this->x*fb.y+fb.x*this->y;
this->y = this->y*fb.y;
return *this;
}
//友元函数
friend Fraction operator+(const Fraction &fa, const Fraction &fb);
friend Fraction operator*(const Fraction &fa, const Fraction &fb);
friend Fraction operator/(const Fraction &fa, const Fraction &fb);
friend istream& operator>>(istream &is, Fraction &fb);
friend ostream& operator<<(ostream &os, const Fraction &fb);
private:
int x;//分子
int y;//分母
};
Fraction operator*(const Fraction &fa, const Fraction &fb)
{
return Fraction(fa.x*fb.x/*分子*/,fa.y*fb.y/*分母*/);
}
Fraction operator/(const Fraction &fa, const Fraction &fb)
{
return Fraction(fa.x*fb.y/*分子*/,fa.y*fb.x/*分母*/);
}
istream& operator>>(istream &is, Fraction &fb)
{
return is>>fb.x>>fb.y;
}
ostream& operator<<(ostream &os, const Fraction &fb)
{
return os<<fb.x<<"/"<<fb.y;
}
int main()
{
Fraction fa;
Fraction fb;
cout<<"请输入第一个分数:";
cin>>fa;
cout<<"请输入第二个分数:";
cin>>fb;
cout<<(fa==fb)<<endl;
cout<<(fa>fb)<<endl;
fa += fb;
cout<<fa<<endl;
return 0;
}
编译器对类类型数据的双目运算符的处理(加法为例)
①当编译器遇到fa+fb的代码,就会先去fa对应的类类型Fraction中寻找一个成员函数: operator+(const Fraction &),如果有这个成员函数,就调用该函数计算 fa+fb ===> fa.operator+(fb);
②如果Fraction类中没有对应的成员函数,就去寻找一个全局函数: operator+(const Fraction &,const Fraction &),如果有这个全局函数,就调用该函数计算 fa+fb ===> operator+(fa,fb);
③若都没没有找到,则编译器报错
== != > < >= <= :返值为bool类型
= += -= :返回第一操作数的引用
类类型和基本类型之间的运算(依据编译器对类类型的双目运算符的处理)
①Complex+浮点数:可以重载成成员函数/全局函数
②浮点数+Complex :只能重载成全局函数
注意:当类中有拷贝构造函数, =赋值运算符才需要重载。一个类如果没有显示定义赋值运算符重载,编译器也会自动生成一个,完成对象按字节序的值拷贝。
对于输入输出运算符的重载(cout<< cin>>),由于cout和cin是由C++预定义的类ostream和istream的成员,不能修改,因此开发者只能通过全局函数重载,将重载函数设置为友元函数。
①cin>>a
1.先去cin对应的类istream中寻找一个成员函数:istream& operator>>(a的引用类型)
2.如果在istream没有,则去寻找一个全局函数:istream& operator<<(istream &is, a的引用类型);
3.若两则都么有,则报错
②cout << a
1.先去cout对应的类ostream中寻找一个成员函数:ostream& operator<<(a的常引用类型)
2.如果在ostream没有,则去寻找一个全局函数:ostream& operator<<(ostream &os,a的常引用类型);
3.若两则都么有,则报错
三、隐式转换
C++中的explicit关键字只能用于修饰只有一个参数的构造函数,它的作用是表明该构造函数是显示的, 而非隐式的。implicit关键字是隐藏的, 类构造函数默认情况下即声明为implicit(隐式).
当使用非本类类型去初始化一个新对象时,先尝试使用初始化数据作为参数去调用构造函数构造成本类型对象,要支持该语法类中必须具有以初始化数据类型作为参数的单参构造函数,并且构造函数声明为隐式的。
如果不希望C++编译器使用该隐式转换,可以在类的构造函数声明前加 explicit 关键字,表明构造函数是显式的,非隐式的;
class Complex{
public:
//explicit Complex(double r=0.0,double i=0.0):real(r),imag(i){}
Complex(double r=0.0,double i=0.0):real(r),imag(i){}
...
private:
double real;//实部
double imag;//虚部
};
Complex c = 2.1; //隐式转换 ===> Complex(2.1): Complex c = Complex(2.1);
用2.1初始化c, 编译器会先使用2.1作为参数去调用构造函数构成Complex类型的对象,将2.1传给参数r,又由于i可以默认为0,并且构造函数为隐式的,所以最终real为2.1,imag为0。若用explicit声明构造函数为显示的,那么最后一行将会报错。explicit关键字的作用就是防止类构造函数的隐式自动转换。
四、单目运算符的重载
C++中单目运算符有:++ -- ! -
编译器对单目运算符(#)的处理:对于运算符的位置先后,编译器有着不同的处理。
①#对象 (++对象、--对象)
1.先去成员函数中找一个operator#()函数
2.若没有则去全局函数中找一个operator#(对象)
3.若两则都么有,则报错
②对象#(对象++、--对象)
1.先去成员函数中找一个operator#(int)函数,这里int是一个哑元
2.若没有则去全局函数中找一个operator#(对象,int)
3.若两则都么有,则报错
class Float{
public:
Float(double d=0.0):data(d){}
//重载-号(负号)
Float operator-()
{
return Float(-this->data);
}
//重载前++
Float& operator++()
{
++this->data;
return *this;
}
//重载后++
Float operator++(int)
{
return Float(this->data++);
}
//重载==
bool operator==(const Float &d)
{
//浮点数不应该直接判等
//return this->data==d.data;
return this->data-d.data>-1e-15 && this->data-d.data<1e-15;
}
//+=
Float& operator+=(const Float &d)
{
this->data += d.data;
return *this;
}
friend ostream& operator<<(ostream &os,const Float &i);
friend Float operator--(Float &i,int);
friend Float& operator--(Float &i);
private:
double data;
};
ostream& operator<<(ostream &os,const Float &i)
{
return os<<i.data;
}
//重载后--
Float operator--(Float &i,int)
{
return Float(i.data--);
}
//重载前--
Float& operator--(Float &i)
{
--i.data;
return i;
}
int main()
{
Float f1(1.1);
Float f2(1.1);
cout<<(f1==f2)<<endl;
//f2 = ++f1;
//f2 = -f1;
f2 += f1;
cout<<f1<<endl;
cout<<f2<<endl;
return 0;
}
五、运算符重载的限制
①以下运算符不能重载 :?: . :: sizeof && || & |
②不能通过连接其他符号(不是C++支持的运算符)来创建新的操作符:比如operator@
③不能改变运算符的特性,符合对象的运算属性
④不能重载基本类型的运算,重载时至少要有一个是类类型
⑤只能重载成全局函数的运算符
1.第一个操作数是C++预定义类型 >> << (输入/输出)
2.第一操作数是基本类型:整数+类类型(基本数据类型在前面)
⑥只能重载成成员函数的运算符
1.赋值运算符:= (+= -= *=)
2.数组对象(把对象当数组使用):[ ]
3.强转,函数对象(把对象当函数使用):( )
4.指针对象(把对象当指针使用):-> *
六、特殊运算符的重载
1.数组对象
当对象遇到[ ]运算符时,在成员函数中找一个 operator[](int xxx)。
//数组对象,重载[]
int& operator[](int n)
{
return this->pdata[n];
}
2.强转和函数对象
①强转
当编译器遇到 (类型)对象 语法时,会在对象对应的类中寻找一个 operator 类型() 的成员函数,找不到就按默认强转或者报错来处理。
//重载(强转) ------ (int)得到数组第一个元素
operator int()
{
return this->pdata[0];
}
②函数对象
当编译器遇到 对象(实参) 语法时,会在对象对应的类中寻找一个 operator()(参数) 的成员函数,找不到就报错。
//重载() ----- 函数对象 ---- 数组求和
int operator()(const MyArray &arr)
{
int sum = 0;
for(size_t i=0;i<arr.len;i++)
{
sum += arr.pdata[i];
}
return sum;
}
3.指针对象
①当编译器遇到 对象-> 语法时,就会去对象对应的类中寻找一个 operator->() 的成员函数,找不到就报错。
②当编译器遇到 *对象 语法时,就会去对象对应的类中寻找一个 operator*() 的成员函数,找不到就报错。
使用指针对象把对象当指针用,目的是管理其他对象的指针。如果指针对象再结合模板的语法,就是可以实现管理各种类型指针的智能指针。
class A{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}
void show()
{
cout<<"show A"<<endl;
}
};
//实现一个指针对象,管理A类型的指针
class myauto_ptr{
public:
myauto_ptr(A *p=NULL):pdata(p)
{
cout<<"myauto_ptr()"<<endl;
}
~myauto_ptr()
{
cout<<"~myauto_ptr()"<<endl;
if(this->pdata)
delete this->pdata;
}
//重载->运算符
A *operator->()
{
return this->pdata;
}
//重载*运算符
A& operator*()
{
return *this->pdata;
}
private:
A *pdata;//要管理的指针
};
int main()
{
A *pa = new A;
//使用指针对象管理pa ----- 无需再释放pa
myauto_ptr ap(pa);
//通过指针对象访问管理的指针指向的成员
ap->show(); //ap->show():ap.operator->()->show();
//解引用指针对象得到管理对象
(*ap).show(); //(*ap).show():ap.operator*().show();
return 0;
}