运算符重载是一种形式的c++多态。c++中用户用户能够定义多个名称相同但特征标(参数列表)不同的函数的。这种被称为函数重载或函数多态,旨在让我们能够使用同名的函数来完成相同的基本操作。运算符重载将重载的概念扩展到运算符上,允许赋予c++运算符多种含义。c++根据操作数的数目和类型来决定采用哪种操作。
c++允许运算符重载扩展到用户自定义的类型。例如,允许使用+将两个对象相加。编译器根据操作数的数目和类型决定使用哪种加法定义。重载运算符可使代码就看起来更自然。
将两个数组相加是一种常见的运算
for(int i = 0 ; i < 20 ;i++)
evening[i] = sam[i] + janet[i];
但在c++种,可以定义一个表示数组的类,并重载+运算符
可以使用这样的语句
evening = sam + janet;
这种简单的加法表示法隐藏了内部机理,并强调了实质。
要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下
operatorop(argument-list)
op:必须是有效的c++运算符,不能虚构一个新的符号
注意:对于内置的数据类型的表达式的运算符不可能改变的,不要滥用运算符重载
多数c++运算符都可以重载,重载的运算符不必是成员函数,但必须至少有一个操作数是用户定义的类型。
重载限制
1、重载后的运算符必须至少一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。 因此,不能将减法运算法(-)重载为计算两个double值的和,而不是它们的差。 虽然这种限制将对创造性有所影响,但可以确保程序正常运行。
2、使用运算符是不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成使用一个操作数 同样,不能修改运算符的优先级。因此将加法运算符重载成将两个类相加,则新的运算符与原来的加号具有相同的优先级
3、不能创建新的运算符,例如,不能定义operator**()函数来表示求幂
4、不能重载下面的运算符:
sizeof:sizeof运算符
. :成员运算符
.* :成员指针运算符
:: :作用域解析运算符
?: :条件运算符
typeid :一个RTTI运算符
const_cast :强制类型转换运算符
static_cast :强制类型转换运算符
dynamic_cast :强制类型转换运算符
reinterpret_cast :强制类型转换运算符
5、大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载
= :赋值运算符
() :函数调用运算符
[] :下标运算符
-> :通过指针访问类成员的运算符
部分可重载的预算符 |
|||||
+ |
- |
* |
/ |
% |
^ |
& |
| |
~= |
! |
= |
< |
> |
+= |
-= |
*= |
/= |
%= |
^= |
&= |
|= |
<< | >> |
>>= |
<<= |
== |
!= |
<= | >= |
&& |
|| |
++ |
-- |
, |
->* |
-> |
() |
[] |
new |
delete |
new[] |
delete[] |
常见运算符重载代码实现
#include
using namespace std;
class Data
{
//一般会把全局运算符重载函数 声明成友元函数
friend int operator+(Data &ra,Data &rb); //class +class
friend int operator+(int a,Data &rb);//int + class
public:
Data(int n=0):data(n){}
void show(){cout<<"data:"<a + m2.a; //直接访问私有成员
return temp;
}
// A++ 运算符重载 -->d1.operator++(int)
int operator++(int)
{
return a++;
}
//++A -->d1.operator++()
int operator++()
{
return ++a;
}
//!A 逻辑取反 -->
bool operator!()
{
return !(this->a);
}
//~A 位取反
int operator~()
{
return ~(this->a);
}
//小括号运算符重载
int operator()(int val)
{
this->a = val;
return this->a;
}
//new运算符重载
//new -->实际上 malloc函数 + 类的构造函数
// void* operator new(std::size_t n)
void* operator new(size_t n)
{
//如果是我们自定义 new运算符重载,那么自己搞malloc函数 + 类的构造函数
cout<<"operator new(int val)"<a + m2.a;
// return temp;
// }
private:
int a;
};
//全局函数运算符重载
int operator+(Data &ra,Data &rb)
{
return ra.m_a + rb.m_a; //可以直接通对象访问自己的私有成员
}
int operator+(int a,Data &rb)
{
return a + rb.m_a;
}
//友元函数:编译器翻译成:operator==(d1,d2);
// bool operator==(Data &ra,Data &rb)
// {
// return ra.a == rb.a;
// }
//逻辑运算符重载
//友元函数:编译器翻译成:operator||(d1,d2);
//bool operator||(Data &ra,Data &rb)
//{
// return rb->a || ra.a;
//}
int main()
{
Data m1(10);
Data m2(20);
//1、C++编译器编译的时候自动转换成类内成员函数
// m1.operator+(m2); //该函数需要在类内重新定义实现
Data m3 = m1 + m2;
//结果由运算符函数重载的返回值类型来决定--返回值的类型是Data类型不是int类型
// int num = m1 + m2; //错误
m3.show();
//1、成员函数重载
//C++编译器 在编译的时候会自动将这些运算符表达式 转换成 对应的 运算符函数,那么我们需要做的运算符重载就是将 这些运算符函数重新实现
// 函数的调用者 运算符 函数的参数
// int ret = mya + myb;
//实际上编译器 最后会编译成这样 本质: int ret = mya.operator+(myb);
// int ret = mya. operator+(myb);
//编译器会转换成 Data myc = mya.operator+(myb)
// Data myc = mya + myb;
// cout<
++的重载
符号在后,先用再加 A++转成员函数重载--》A.operator++(int) --->转非成员函数 operator++(A的类型,int)
符号在前,先加再用 ++A转成员函数重载--》A.operator++() --->转非成员函数 operator++(A的类型)
对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。
拿加法运算符为例,加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作符都作为参数来传递。
注意:非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需要的参数数目少一个,因为其中一个操作数是被隐式地传递地调用对象。
记住,在定义运算符时,必须原则其中的一种格式,而不能同时选择在两种格式。因为这两种格式都同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。
那么哪种格式最好呢?对于某些运算符来说,成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)。
c++控制对类对象私有部分的访问。通常,公有类方法提供唯一的访问途径,但是有时候指针限制太严格,以致于不适合特定的编程问题。在这种情况下,c++提供了另外一种形式的访问权限:友元
友元有三种
1、友元函数
2、友元类
3、友元成员函数
通过让函数称为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
友元的目的:让一个函数或者类访问另一个另类中私有成员
友元的作用:友元提供了在不同类的成员函数之间,类的成员函数与一般函数之间进行数据共享的机制,通过友元,一个普通函数或另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但破坏了类的封装性和数据的隐藏性,导致程序可维护性变差,因此一定要谨慎使用。
创建友元函数的第一步是将其原型放在类种,并在原型声明前嘉善该关键字friend
格式
friend 类型 函数名(形式参数)
第二步时编写函数定义。因为它不是成员函数,所以不要使用限定符。另外,不要在定义中使用关键字friend
类的友元函数是非成员函数,其访问权限与成员函数相同。
友元是否有悖于OOP
有一些人可能会认为友元违反了OOP数据隐藏的原则,因为友元机制允许非成员成员函数访问私有数据。然而,这个观点太片面了。相反,应将友元函数看作是类的扩展接口的组成部分。也就是说,前一个要求有友元函数,后一个使用成员函数,这是c++句法的结果,而不是概念上的差别。通过使用友元函数和类方法,可以用同一个用户接口表达这两种操作。另外请记住,只有类声明可以决定哪一个函数是友元,因此类声明依然控制了哪些函数可以访问私有数据。总之,类方法和友元只是表达类接口的两种不同机制。
#include
using namespace std;
class Complex{
//如果该函数使用了类内的私有成员,声明为 友元函数
friend ostream& operator<<(ostream &out,Complex &ra);
friend istream& operator>>(istream &in,Complex &ra);
public:
Complex(double real=0.0,double imag=0.0){
this->real =real;
this->imag =imag;
}
double getReal(){
return this->real;
}
double getImge(){
return this->imag;
}
private:
double real;
double imag;
};
//全局函数运算符重载
Complex operator-(Complex &ra,Complex &rb)
{
double r = ra.getReal() - rb.getReal();
double i = ra.getImge() - rb.getImge();
return Complex(r,i);
}
Complex operator+(Complex &ra,Complex &rb)
{
double r = ra.getReal() + rb.getReal();
double i = ra.getImge() + rb.getImge();
return Complex(r,i);
}
//标准输出重载
#if 1
//需要清楚: 为什么 输出类的对象 用的是引用 答案:系统设计出来的标准输出类对象 用了类似单例设计模式,确保这个工程中只有一个标准输出对象
//ostream& operator<<(ostream &,Complex &ra) //也可以写成占位参数
ostream& operator<<(ostream &out,Complex &ra) //ostream &out = cout,Complex &ra = myd
{
// out<<"real:"<>(istream &in,Complex &ra) //istream &in = cin ,Complex &ra = myd
{
//输入的内容自定义
in>>ra.real>>ra.imag;
// in>>ra.real;
return in;
}
#endif
int main(){
Complex mya(10.0,20.0);
Complex myb(100.0,200.0);
//编译器转换成: Complex myc = operator-(mya,myb)
Complex myc = mya - myb;
Complex myd = mya + myb;
//cout<<"real:"<>(myd) 2、全局函数 operator>>(cin,myd)
//第一种写法 错误,因为标准输出类 是 系统已经定义好的 所有 标准输出 运算符>> 函数重载 采用 全局函数
// cin>>myd; // 2、全局函数 operator>>(cin,myd)
// cout<
友元声明可以位于公有、私有或者保护部分,其所在位置无关紧要。
需要使用前向声明