Re:从零开始的 C++ 入門篇(六)类和对象·第三篇:运算符重载

◆ 博主名称: 晓此方-ZEEKLOG博客
大家好,欢迎来到晓此方的博客。
⭐️C++系列个人专栏:
⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰
目录
0.1概要&序論
みなさん、久しぶりです!,这里是此方,好久不见!上一篇我们介绍了类的默认成员函数:构造函数和析构函数。本期此方将为大家带来运算符重载的系统讲解。每一个成功的人都有一段不为人知的时光,这段时光称为”修炼“。加油!类与对象中相对困难的部分即将告一段落啦!
一,运算符重载
1.1运算符重载的意义
运算符都是针对内置类型的,对自定义类型而言,不能简单使用运算符。这样,对于自定义类型的各种运算将无从入手。于是C++增加了运算符重载的概念。
当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
1.1.1运算符重载应当有意义
一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义(了解两个日期之间间隔天数),但是重载operator+就没有意义。
1.2运算符重载的定义
- 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。(前面的流插入流提取。就是一种内置的运算符重载)
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
- 万不可连接运算符中不存在的符号进行重载:如operator@。
- 运算符重载和函数重载没有任何关系,但是两个及以上的运算符重载构成函数重载。
1.3运算符重载的创建
1.3.1以Date类为例
class Date{ pubilc: Date(int year,int month,int day) { _year=year; _month=month; _day=day; } private: int _year; int _month; int _day; }1.3.2创建重载运算符“==”
要让d1==d2,就让d1的所有成员变量==d2所有成员变量。返回值设置为bool类型。传递参数两个Date类类型。
bool opreator==(Date d1 ,Date d2) { if( d1._year==d2._year &&d1._month=d2._month &&d1._day=d2._day) return true; else return false; }1.3.3无意义创建避免
重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义来构建防御性变编程,如:
int operator+ (int x, int y) { return x-y; }1.4运算符重载的调用
1.4.1显式调用
直接把重载后的运算符看作是一个函数,传入参数,如下:
int main() { Date d1(2025,2,23); Date d2(2025,3,26); operator==(d1,d2); return 0; }1.4.2隐式调用
把重载后的运算符看作兼容该类类型的运算符,采用运算符语法。
对于二元运算符重载:左侧对象传给第一个参数,右侧传输给第二个参数。隐式调用实际上等同于显式调用,会自动转化为显示调用。
d1==d2;1.4.3重载为成员函数的调用
下文会讲为什么会重载成成员函数,这里介绍重载为成员函数后的特殊调用方法
如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
class Date{ pubilc: bool opreator==(Date d){ if( _year==d._year &&_month=d._month &&_day=d._day ) return true; else return false; } //........ }显示与隐式调用:
隐式调用方式不变,他会自动转换成显示调用,d1传递给this指针,d2传递给d
int main() { Date d1(2025,2,23); Date d2(2025,3,26); d1.operator==(d2); d1==d2; return 0; }1.4.4调用常见问题:访问限制
由于类的成员变量被private修饰,无法在类外直接调用。
解决办法:
最挫的方法:成员变量公有化仿JAVA常用方法:由类提供get()函数后面会讲:友元函数 最好的方法:重载为成员函数
方法一:去掉private修饰,让public修饰的作用域覆盖成员变量。不建议,会让类属性被随意修改。
class Date{ pubilc: Date(int year,int month,int day){ _year=year; _month=month; _day=day; } //private: int _year; int _month; int _day; }方法二:由类提供get()函数,在对象外调用getyear就可以通过返回值间接得到_year。
int Getyear() { return _year; }方法四:重载为成员函数,省去一切麻烦,最推荐使用该方法,大部分的运算符重载函数都是会设计成成员函数。
class Date{ pubilc: bool opreator==(Date d2){ if( _year==d2._year &&_month=d2._month &&_day=d2._day ) return true; else return false; } private: int _year; int _month; int _day; };1.4.5运算符重载调用优先级
//.......... bool operator==(const Date& d){ return _year == d._year && _month == d._month && _day == d._day; } //private: int _year; int _month; int _day; }; bool operator==(const Date& d1, const Date& d2){ return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; }如果在类的内部和外部同时创建一个相同的运算符重载。
- operator==(d1,d2)显式调用只会调用全局函数。
- d1==d2隐式调用对调用成员函数还是全局函数会进行重载决议,但往往成员函数会获胜。
1.5五大不可重载运算符
- 域访问限定符。”::“
- 计算类型大小。“sizeof”
- 三元操作符。“?:”
- 类访问操作符。“.“
- 指向成员变量或成员函数的指针。”.*“
原因:
这五个运算符被语言标准明确规定为不可重载,原因并不是随意的,而是出于语法完整性、可解析性以及编译期语义安全的系统性考虑。可以从一个统一的原则来理解:
凡是参与“语言语法结构本身”或“编译期语义建模”的运算符,都不可重载。
1.6指向成员变量或成员函数的指针(加餐内容)
上文提到过”.*“这个运算符,想必大家都比较陌生,这是C++相比C语言新增的内容。以下给出详细介绍:
1.6.1补充一:成员函数指针
C/C++中一般函数指针举例:
void (*)(int a,int b);成员函数指针类型:加上了域访问限定符,标志该函数指针来自哪里,是什么类的成员函数。
void (A::*)(int a,int b);1.6.2补充二:成员函数指针重命名
重命名:注意函数指针类型的重命名相比一般重命名不同:这里将void (*)(int a,int b)重命名为PF
typedef void (*PF)(int a,int b);C/C++中成员函数指针重命名:
typedef void (A::*PF)(int a,int b);1.6.3补充三:定义成员函数指针类型变量
定义一个成员函数指针类型的变量:
void (A::*pf)(int a,int b)=nullptr;//方法一 PF pf=nullptr;//方法二1.6.4补充四:用成员函数指针回调
取得现有成员函数的指针,与普通函数指针不同,成员函数的指针取得,需要加上两个步骤:确定类域和取地址。
pf=&A::func;回调函数:一般函数的回调方式:
pf=func(); //将函数的指针放入函数指针变量中。 *pf() //取地址函数指针变量进行回调成员函数由于函数的第一个参数是this指针,但是this指针不能显示传参,所以要一个对象进行辅助,用这个对象调用这个函数。
A aa; (aa.*pf)()于是,就出现了.*运算符。
成员函数的回调在日常代码中使用的非常非常少。C语言时期学过的qsort函数就是用回调的,后面C++还有很多更好的解决办法,一般的,我们会尽可能避免使用函数指针。
好了,本期内容就到这里,我是此方,我们下期再见。じゃあね〜