C++ -运算符重载
📚 前言
本文内容:
本来就想写赋值运算符重载的,结果发现运算符重载都没写🤣,那就先来看看什么是运算符重载吧。
对内置类型,下面这些操作是很常见的:
int a, b;
a = 1, b = 5; // 赋值
a += b; // 加等
cout << a; // 流插入
而对于类,有时也需要进行类似的操作,于是C++使用operator关键字对运算符进行了重载,使类对象也可以像内置类型一样使用运算符。
讲概念有点太空了,来看看具体的例子吧——日期类:
根据上篇文章讲的构造、析构、拷贝构造,先搭个架子:
class Date
{
public:
Date(int y = 1, int m = 1, int d = 1) // 全缺省的默认构造
{
_year = y;
_month = m;
_day = d;
}
Date(const Date& d) // 拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print(){cout << _year << '/' << _month << '/' << _day << endl; } //一个不重要的打印函数
private:
int _year;
int _month;
int _day;
};
🔄 默认赋值运算符重载
现在我想将一个日期赋值给另一个日期,该怎么做?赋值运算符重载(operator=
)。
幸运的是,由于将一个类对象赋值给另一个类对象的操作太过常见,编译器提供了默认的赋值运算符重载。
其行为和拷贝构造类似:
- 对内置类型,逐字节拷贝。
- 对自定义类型,调用对应的赋值运算符重载
于是,就可以这样做:
Date day1(2000, 9, 13);
Date day2;
day2 = day1; // 将day1赋给day2
day2.print();
运行结果:
2000/9/13
为了便于理解,还是来写一下吧:
运算符重载的本质是一个具有特殊名字的函数,既然是函数,首先要确定三点:函数名、参数列表、返回类型
规定:函数名为operator
加需要重载的运算符。
而此处,函数名就是 operator=
。
再来看看返回类型:
cout << typeid(day2 = day1).name() << endl;
运行结果:
class Date
返回日期类对象。但是不是日期类对象的引用呢?
运算符重载这块,如果有不清楚的,可以用 int
来对应其行为。
例如,下面就验证了=
返回的是引用:
void Test()
{
int num;
int& ref = (num = 1);
ref = 666;
cout << num;
}
运行结果:
666
可以看见,修改了返回值的引用 ref
,将num
也修改了。
⚙️ 常见运算符返回类型
掌握了返回类型,就初步掌握了如何写运算符重载,所以我在这里稍微整理了一下:
-
赋值运算符 (
=
)-
返回:左操作数的引用。
-
int a; int& ref = (a = 1); // ref 是 a 的引用 ref = 666; cout << a;
运行结果:
666
-
-
复合赋值操作符(
+=
,-=
…)- 返回:左操作数的引用。
- 道理同上
-
算术运算符 (
+
,-
,*
,/
,%
)-
返回:一个临时的值(右值)。
-
int a; const int& ref = a + 6;
ref
是临时变量的const
引用,相当于延长了临时变量的生命周期如果想这样写:
int& ret = a + b;
会报错,因为引用不能放大权限:
-
-
自增自减运算符 (
++
,--
)- 前缀形式:返回左操作数的引用。
- 后缀形式:返回一个临时的值(右值)。
-
int a = 666; int& ref1 = ++a; const int& ref2 = a++;
ref1
是a
的引用,ref2
是临时变量(存放a
原来的值)的引用
-
关系运算符 (
==
,!=
,<
,>
,<=
,>=
)- 返回:一个布尔值(
bool
)。 -
如果想这样写:int a = 666; bool result = (a > 6); // result 是 true
会报错,因为引用不能放大权限:bool& ref = (a > 6);
- 返回:一个布尔值(
-
逻辑运算符 (
&&
,||
,!
)- 返回:一个布尔值(
bool
)。 -
这个和关系运算符是一样的。int a = 666; int b = 666; bool result = (a > 0 && b > 0); // result 是 true
- 返回:一个布尔值(
-
位运算符 (
&
,|
,^
,~
,<<
,>>
)-
返回:一个临时的值(右值)。
-
int a = 666; int result = (a & 0); const int& ref = (a & 0);
如果想这样写:
int& ref = (a & 0);
会报错,因为引用不能放大权限:
-
-
流插入,流提取(
<<
,>>
)-
<<
返回ostream
的引用,>>
返回istream
的引用(在这里这样认为就够了) -
int a; cout << typeid(cin >> a).name() << endl; cout << typeid(cout << "").name() << endl;
运行结果:
class std::basic_istream<char,struct std::char_traits<char> > class std::basic_ostream<char,struct std::char_traits<char> >
-
-
条件运算符 (
? :
)- 返回:取决于两个表达式。
-
int a = 666; int b = 0; int& ref1 = (a > b) ? a : b; // 这样可以 int& ref2 = (a > b) ? a++ : b; // 这样不可以 int& ref3 = (a > b) ? a : b++; // 这样不可以
-
逗号运算符 (
,
)- 返回:取决于最后一个表达式。
-
int a = 666; int& ref1 = ("HaHa", a); // 这样可以 int& ref2 = (a, a++); // 这样不可以
-
函数调用运算符 (
()
), 下标运算符 ([]
), 成员访问运算符 (->
,.
)- 这些我也不清楚,应该是取决于对应函数或对象的具体实现吧。
✍️ 赋值运算符重载
额。。扯远了,我们再来看看怎么写赋值运算符重载。
首先函数名和返回类型我们已经解决了,那么剩下的就是参数。
关于这点,我认为一般传引用就行,如果不修改,那就再加个 const
。而对于栈之类需要深拷贝的类,可以考虑用一用传值传参,然后再交换,这个在之后简单模拟实现容器的时候应该会讲。
对于日期类,就是将一个日期赋给另一个日期:
Date& operator=(Date& tgt, const Date& src);
但这样写会有问题,因为定义在类外面的函数不能访问类的私有成员。
所以一般会将运算符重载写进类里面:
class Date{
public:
Date& operator=(const Date& src);
...
}
再看看实现:
Date& Date::operator=(const Date& src)
{
_year = src._year;
_month = src._month;
_day = src._day;
return *this;
}
调用的话有两种方式,一种是将其当成函数调用:
day2.operator=(day1);
用得更多的是将其当成运算符调用:
day2 = day1;
如果写对了,那么应该可以连续赋值,并且返回值可以调成员函数,来验证一下:
void Test()
{
Date day1(2000, 9, 13), day2, day3;
(day3 = day2 = day1).print();
}
运行结果:
2000/9/13
💡 其他运算符重载示例
再来写写其他的。
比如 加 +
:
Date operator+(int add);
具体实现。。懒得搬了,就当我写了吧。
重点看看前置++和后置++:
这两个都是单目操作符,而为了将这两个区分,C++直接给出了规定:
这个——operator++()
——是前置++。
这个——operator++(int)
——是后置++。
而返回类型,根据刚刚的总结,前置是自增后引用,后置是自增前的临时变量:
Date& operator++();
Date operator++(int);
具体实现可以直接复用 operator+
。
再看看关系运算符,这里总共有五个,但只需要写两个:
bool operator<(const Date& d);
bool operator==(const Date& d);
然后,>
就是 !(<
||==
) ,>=
就是。。。总之复用就行。
最后来看看流插入和流提取。
假设已经实现好了,那么应该像下面这样使用:
cin >> day;
cout << day;
很明显,操作符的左边不是类对象,因此不能写成成员函数,我们可以将其定义为全局,并声明为类的友元:
class Date{
friend istream& operator>>(istream& in , const Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
...
}
<<
的具体实现:
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
注意这里只能返回 ostream
的引用。
再来看看取地址操作符重载和const取地址操作符重载:
大概长这样:
Date* operator&() { return this; }
const Date* operator&() const { return this; }
这两个也是类的默认成员函数,如果不写,编译器会自动生成。
一般情况下,用编译器自动生成的就行,如果你不想让别人取地址,也可以自己写一个,然后返回空或者不存在的地址。
⚠️ 注意事项
最后来看看运算符重载的注意事项:
- 名字由operator和后⾯要定义的运算符共同构成
- 不能创造新的操作符
.
.*
?:
::
sizeof
不能运算符重载- 不能重载内置类型,如
int operator+(int a, int b);
- 双目操作符,左边的传入第一个参数,右边的传入第二个参数
- 自增自减,()为前置,(int)为后置
- 流插入、流提取,需要重载为全局
- 重载后最好有意义(一个日期可以减一个日期,但加?)
希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!
原文地址:https://blog.csdn.net/2401_86587256/article/details/143982929
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!