运算符函数
在C++中会把运算符当做函数处理,一个表达式,其实可能调用了很多运算符函数来完成计算,这种特性对内建类型没有用,但是对于自建类型而言,通过设计运算符函数能够进行个性化运算,以此提高代码的可读性、易用性,例如string类
Ⅰ.运算符函数的格式:
'#'表示运算符,'O'表示运算符对象
-
单目运算符: #O O#
成员函数: [] O::operator#(void) { } 返回值不确定,唯一的参数就是调用者本身 全局函数: [] operator#(O& o) { } 某种运算符成员函数、全局函数只能同时实现一个,不能一起实现
-
双目运算符: a # b
注意:左操作数是运算符函数的发起者 成员函数: [] A::operator#(B& b) { } 全局函数: [] operator#(A& a,B& b) { }
-
运算类的双目运算符 O是类名
成员函数:a + b const O O::operator+(const O& b)const { return O(x+b.x,y+b.y); } 全局函数: a + b const O operator+(const O& a,const O& b) { }
Ⅱ.友元
在实现类的全局运算符函数时,可能会使用到类内的私有成员,此时全局函数是没有访问权限的,如果改变私有为公开会破坏类的封装性,如果提供公开的访问函数又非常麻烦,最好的方式是给该全局函数给予独家授权,让其能够访问类内私有成员,这种行为称为把该全局函数设置为友元函数
方式:在类内对全局函数声明,并在声明前加friend
friend cosnt O operator+(const O& a,const O& b);
Ⅲ.输入输出运算符
在C++中 << >> 运算符不光是按位左移、按位右移,同时还是cout该类的输出运算符 cin该类的输入运算符
(以下模式背下来)
-
输出运算符
由于 << 运算符的调用者是cout对象,我们是无法在该对象的类中去设计一个输出运算符的成员函数,所以只能实现 << 运算的全局函数ostream& operator<<(ostream& os,const O& t) { return os << "输出格式" ; }
-
输入运算符 cin >> num;
istream& operator>>(istream& is,O& t) { return is << "输入格式" ; }
注意:
-
由于输出、输入是可以连续进行的,所以返回值还应该是ostream、istream引用
-
因为无法在ostream、istream中重载运算符成员函数,所以 << >> 只能重载成全局函数
-
如果在重载全局函数中使用到自己类内私有的成员变量,需要声明为友元函数
-
输出运算符函数中,第二个参数一定要加const,而输入运算符函数中不能加
Ⅳ.运算类的单目运算符
单目:++/-- ! ~ - * & sizeof
成员函数: ~ ! -
O O::operator~(void)const
{
return O(~x,~y);
}
注意:运算对象可以具备常属性,因此需要是常函数,运算的结果只是一个临时值,并且是右值
全局函数:
O operator~(const O& a)
{
return O(~a.x,~a.y);
}
Ⅴ.自变运算符
-
C++的前后自变左右值问题:
能位于赋值运算符= 左边的就是左值,反之为右值
有名称、可以获取到存储地址的称之为左值,反之右值 -
C++前自变: ++num = 10; //成功 num = 10
直接修改原对象,在原对象基础上实现自变,然后将原对象的引用返回,所以操作和返回的一直是原对象,是左值 -
C++后自变: num++ = 10; //报错
先将原对象的数据存储到临时变量中,接着在原对象基础上自变,然后把临时变量以只读方式返回,并且该临时变量执行语句结束后立即销毁了,无法访问,因此结果是右值
++num++; //后自变优先级更高,报错
(++num)++; //先前自变为左值,正确
-
前自变运算符: ++a/--a(四种自变运算符函数背下来,笔试会考)
成员函数:O& O::operator++(void) { ++操作; return *this; }
全局函数:
O& operator++(O& p) { ++操作; return p; }
-
后自变运算符: a++/a--
哑元:在参数列表中增加一个不使用且无形参名的int哑元类型,唯一目的就是用于区分是前自变还是后自变
成员函数:O O::operator++(int) { return O(x++,y++); }
全局函数:
O operator++(O& a,int) { return O(a.x++,a.y++); }
Ⅵ.特殊的运算符重载函数
* -> ( ) [ ] new delete
-
[ ]下标运算符
想让一个类对象当成数组一样使用,可以考虑重载下标运算符,例如:vector
可以考虑在下标重载函数中做非法下标的判断 -
( )函数运算符
重载此运算符,可以让一个类对象当作函数一样使用
注意:
-
( ) \ [ ] 均不能实现为全局运算符函数,只能实现成员函数(C++全局中已经有类似的函数实现,所以不让实现)
-
= 赋值运算符函数也不能实现为全局函数,因为类内本身一定有一个=赋值运算符成员函数
-
解引用* 和访问成员运算符 ->
重载这两个运算符可以让类对象像指针一样使用,智能指针就是通过重载这俩运算符从而像使用指针一样的类 -
new/delete运算符的重载
void* operator new(size_t size)
C++语法要求重载new运算符的参数必须为size_t,编译器会帮助计算出要申请的字节数并传递,返回值必须为void*,编译器会帮助转换成对应类型指针并返回
void operator delete(void* ptr)
C++语法要求重载delete的参数必须为void,编译器帮助转换成void传递
注意:
①new和delete的成员函数、全局函数格式一样;
②如果只是如果只是针对某个类想要重载它的new\delete时,则写为成员函数
③如果想要所有类型都执行重载版本,则实现为全局函为什么要重载new/deldete?
①可以在重载函数中记录每次分配、释放内存的地址、代码情况、次数情况等到日志中,从而方便检查是否出现内存泄漏,以及泄漏位置
②对于字节少、且频繁申请、释放的对象,可以在重载函数中给他多分配点内存从而减少产生内存碎片的可能
内存泄漏:内存无法使用,也无法被释放,当再次需要时只能重新申请,然后又重复以上过程,日积月累后会导致系统中可用的内存越来越少
-
如何尽量避免内存泄漏:谁申请的谁释放,谁知道该释放谁释放
-
如何判断定位内存泄漏:
①查看内存的使用情况,windows 任务管理器 Linux 命令ps -aux
②代码分析工具mtrace,检查malloc、free的使用情况
③封装新的malloc和free函数,记录调用信息到日志中
内存碎片:已经被释放但是又无法继续使用的内存叫做内存碎片,是由于申请和释放的时间不协调导致的,内存碎片无法避免只能尽量减少
如何减少内存碎片:
-
尽量使用栈内存,栈内存不会产生内存碎片
-
不要频繁地申请和释放内存
-
尽量申请大块内存自己管理
Ⅶ.重载运算符的限制
-
不能重载的运算符
:: 域限定符 . 直接访问成员的运算符 ?: 三目运算符 sizeof 计算字节数 typeid 获取类型信息的运算符
-
只能重载为全局函数的运算符
<< 输出运算符 >> 输入运算符
-
只能重载为成员函数的运算符
[] () = ->
-
运算符重载可以自定义运算符执行过程,但是无法改变运算符的优先级
-
运算符的操作数量也不能改变
-
不能发明新的运算符
建议:
-
重载运算符要遵循一致性原则,不要随意改变运算符本身的含义不要忘记实现运算符重载函数的初衷
-
不要忘记实现运算符重载函数的初衷,为了提高可读性,不要随意炫技