前言
✨欢迎来到小K的C++专栏,本节将为大家带来C++运算符重载 — 意义 | 限制 | 方法 | 规则 | 特殊运算符重载 | 应用场景 的分享✨
目录
运算符重载,就是赋予运算符新的含义。它是重载系统内部运算符的函数,是实现类静态多态性的方式之一。它本质上是函数重载。
实际上,我们已经在不知不觉之中使用了运算符重载。列如:我们习惯使用的对整数,浮点数运用+、-、*、/,起始计算机对整数和浮点数的操作过程是不一样的,但由于C++已经对运算符进行了重载,所以才能都适用。
又如<<本来是左移运算符,但在输出操作中,与cout搭配,当作输出流运算符了
一、运算符重载的意义
我们平常常见的算术运算符、逻辑运算符等运算符都是系统库函数里面已经存在的,所以运算符可以直接用于我们常用的数据类型。然而对于我们自定义的类实例化的对象,系统没有与之相关的运算符可以操作,但是为了使我们编写程序时更加便捷,C++提供了一种方式——运算符重载,来满足我们对于类对象进行的操作。
也就是说运算符重载是为了解决类对象之间的运算的,让编译器在遇到对象运算时能按我们要求的进行运算,这就是运算符重载的意义。
二、运算符重载限制
C++中绝大部分的运算符允许重载,少部分不允许重载,详细描述如下
可以重载的运算符
- 算术运算符:+ - * / %
- 自增、自减运算符:++ –
- 位运算符:| & ~ ^ << >>
- 逻辑运算符:|| && !
- 关系运算符:== != < > <= >=
- 赋值运算符:= += -= /= %= &= |= ^= <<= >>=
- 单目运算符:+ - * &
- 动态内存分配:new delete new[] delete[]
- 其他运算符:() -> , []
不能重载的运算符
- . 成员访问运算符
- :: 域运算符
- .* ->* 成员指针访问运算符
- szieof 长度运算符
- ?: 条件运算符
三、运算符重载规则
-
重载运算符函数可以对运算符作出新的解释﹐但原有基本语义不变:
- 不改变运算符的优先级
- 不改变运算符的结合性
- 不改变运算符所需要的操作数
- 不能创建新的运算符
-
语法:
- 一个运算符被重载后,原有意义没有失去,只是定义了相对一特定类的一个新运算符。
- 函数返回值:当前运算符运算结束后产物类型,
int a,int b,a+b
返回int类型- 函数名:
operator
和运算符组成函数名- 函数参数
- 运算符重载函数是类中成员函数:函数参数等于操作数-1
- 运算符重载函数是友元函数:函数参数等于操作数
- 函数体:写运算符的实际想要的运算
- c++类中存在一个赋值的重载函数(默认)
四、运算符重载方法
两种重载方法
重载为成员函数或全局(友元)函数
两种形式的选择时机:
- 左操作数(或者只有左操作数并且)是本类的对象时,可选用成员函数形式。
- 左操作数不是本类的对象,必须采用非成员函数的形式,一般是友元函数。
-
一般单目运算符最好被重载为成员函数;双目运算符重载为友元函数。
- 有些运算符不能重载为友元函数,它们是:=,(),[]和->。
-
具有可交换性的双目运算符最好两种形式都有(成员函数时适用左操作数为本类对象,友元函数时适用左操作数为其他类的对象)。
#include <iostream>
using namespace std;
class MM
{
public:
MM(int i,int j):i(i),j(j){}
void print()
{
cout<<this->i<<"\t"<<this->j<<endl;
}
MM operator+(MM& mm)
{
return MM(this->i+mm.i,this->j+mm.j);
}
friend void operator+=(MM&mm1,MM&mm2) //要修改数据,传入引用
{
mm1.i+=mm2.i;
mm1.j+=mm2.j;
}
protected:
int i;
int j;
};
int main(int argc, char** argv)
{
MM mm1(1,2);
MM mm2(5,8);
MM mm3=mm1+mm2;
mm3.print();
MM mm4=mm1.operator+(mm2); //显示调用
mm4.print();
//友元重载
mm1+=mm2;
mm1.print();
operator+=(mm3,mm2); //显示调用
mm3.print();
return 0;
}
五、C++特殊运算符重载
1. .= () -> [] 只能重载为类成员函数
2. 运算符重载必须存在至少一个自定义类型才能重载
3. . .* ?: :: 不能被重载
4. C++只允许重载已有运算符,不能无中生有
5. 习惯行为:单目运算符采用类的成员函数重载,双目运算符采用友元重载
1、++和–运算符的重载
对于++和–重载,通过增加无用参数(int)标识为后置运算,下面是一个代码示例和示例结果
#include<iostream>
#include<string>
using namespace std;
class MM
{
public:
MM(string name="",int money=0):name(name),money(money){}
MM operator++() //前置
{
this->money++;
return *this;
}
MM operator++(int) //后置
{
return MM(this->name,this->money++);
}
void print()
{
cout<<this->money<<"\t"<<this->name<<endl;
}
protected:
string name;
int money;
};
int main()
{
MM mm1("king",100);
MM mm2=mm1++;
mm2.print();
MM mm3=++mm2;
mm3.print();
return 0;
}
2、C++流对象重载
cout
本质是一个类的对象:ostream
,cin本质也是一个类的对象:istream
- 流重载必须要用友元方式重载
- 流重载尽量使用引用类型
#include<iostream>
#include<string>
using namespace std;
class MM
{
public:
MM(string name="",int money=0):name(name),money(money){}
void print()
{
cout<<this->money<<"\t"<<this->name<<endl;
}
friend ostream& operator<<(ostream& out,MM& object);
friend istream& operator>>(istream& in,MM& object);
protected:
string name;
int money;
};
ostream& operator<<(ostream& out,MM& object)
{
out<<object.name<<"\t"<<object.money<<endl;
return out;
}
istream& operator>>(istream& in,MM& object)
{
cout<<"请输入info:"<<endl;
in>>object.name>>object.money;
return in;
}
int main()
{
MM mm1("king",100);
cin>>mm1;
cout<<mm1<<endl;
return 0;
}
3、C++文本重载
- 所谓文本重载,就是重载后缀,固定写法
- 函数参数:
unsigned long long
(一定是)- 函数名:
operator""
要重载的后缀 —— 一般重载后缀采用下划线系列- 一个运算符或者一个后缀只能重载被重载一次
本例中重载时间中的时分秒,用起来非常简便
#include<iostream>
#include<string>
using namespace std;
unsigned long long operator"" _h(unsigned long long num)
{
return num*3600;
}
unsigned long long operator"" _min(unsigned long long num)
{
return num*60;
}
unsigned long long operator"" _s(unsigned long long num)
{
return num;
}
int main()
{
cout<<1_h+3_min+2_s<<endl;
return 0;
}
4、C++operator实现隐式转换
-
所谓隐式转换就是可以让对象直接赋值给普通对象——便捷获取数据成员的接口
-
模板
operator 隐式转换的类型() { return 数据; }
#include<iostream>
#include<string>
using namespace std;
class MM
{
public:
MM(string name="",int money=0):name(name),money(money){}
void print()
{
cout<<this->money<<"\t"<<this->name<<endl;
}
//隐式转换----->便捷获取数据成员的接口
operator int()
{
return this->money;
}
operator string()
{
return this->name;
}
protected:
string name;
int money;
};
int main()
{
MM mm("king",89);
int money=mm;
string name=mm;
cout<<money<<"\t"<<name<<endl;
return 0;
}
六、运算符重载的应用场景
1、迭代器实现
-
让对象模拟指针的行为
-
模板
#include<iostream> #include<string> using namespace std; int main() { string king="King word!"; string::iterator iter; for(iter=king.begin();iter!=king.end();iter++) { cout<<*iter<<" "; } cout<<endl; }
#include<iostream>
#include<string>
using namespace std;
struct Node
{
int data;
Node* next;
Node(int data=0):data(data),next(nullptr){}
};
class List
{
public:
void push_front(int data)
{
Node* newNode=new Node(data);
if(curSize==0)
tailNode=newNode;
else
newNode->next=frontNode;
frontNode=newNode;
curSize++;
}
class iterator
{
public:
iterator(Node* pmove=nullptr):pmove(pmove){}
iterator operator++(int)
{
return iterator(pmove=pmove->next);
}
bool operator!=(iterator&& object) const
{
return this->pmove!=object.pmove;
}
int operator*()
{
return pmove->data;
}
protected:
Node* pmove;
};
protected:
Node* frontNode=nullptr;
Node* tailNode=nullptr;
int curSize=0;
public:
iterator begin()
{
return iterator(frontNode);
}
iterator end()
{
return iterator(nullptr);
}
};
int main()
{
List list;
for(int i=0;i<=10;i++)
{
list.push_front(i+1);
}
List::iterator iter;
for(iter=list.begin();iter!=list.end();iter++)
{
cout<<*iter<<" ";
}
return 0;
}
2、函数包装器
- 把函数指针包装成一个对象
#include<iostream>
#include<string>
using namespace std;
class Func
{
using Fun=void(*)();
public:
Func(Fun pf):pf(pf){}
void operator()()
{
pf();
}
protected:
Fun pf;
};
void print()
{
cout<<"我是函数包装器"<<endl;
}
int main()
{
Func x(print);
x(); //通过对象调用函数
return 0;
}
3、智能指针
- 可以实现内存的自动申请与释放
#include<iostream>
#include<string>
using namespace std;
class King
{
public:
King(int* ptr):ptr(ptr){}
~King()
{
if(ptr!=nullptr)
{
delete ptr;
ptr=nullptr;
}
}
int* operator->()
{
return this->ptr;
}
int operator*()
{
return *this->ptr;
}
protected:
int *ptr;
};
int main()
{
King k(new int(9999));
cout<<*k<<endl; //通过对象访问数据
return 0;
}
总结
通过运算符重载,可以使自定义类型的操作更加直观和方便。但在进行运算符重载时,需要遵守一定的规则和限制,以确保代码的正确性和可维护性。