1.运算符重载
2.const成员函数
3.取地址操作符及const取地址操作符重载
1.运算符重载
1.1运算符重载
c++为了增强代码的可读性,引入了运算符重载,运算符重载函数是具有特殊函数名的函数。
函数名:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator运算符(参数列表)
注意:
1.只能重载c++中已存在的操作符,不能创建新的操作符,例如operator@
2.运算符重载函数必须有一个类类型参数,可以是类类型的引用(类指针类型是不可以的,this是个特例,成员函数隐式类指针类型形参是可以的)
3.以下5种运算符不能重载
.*
::
sizeof
?:
.
4.运算符重载函数的形参必须要按顺序和运算符的操作数一一对应,所以操作数个数不能变
5.重载的操作符将继承原操作符的优先级和结合性
6.注意:类的成员函数的第一个参数是this,因为这个this看不见,所以成员函数的形参看起来比所重载的操作符的操作数的数目少一,例如加操作符,它是双目操作符,有两个操作数,但是只显式写1个形参。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 全局的operator==
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = 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;
}
void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}
int main()
{Test();return 0;
}
这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
解答:这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 21), d2(2024, 4, 21);cout << (d1 == d2);return 0;
}
7.运算符重载函数也可以以函数的形式使用。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 21), d2(2024, 4, 21);cout << (d1.operator==(d2));return 0;
}
但是一般不会这么写!!!
1.2赋值运算符重载
1.为了支持连续赋值,我们要将被赋值的操作数(也就是左操作数)作为返回值,所以返回*this。
2.由于拷贝对象会使效率降低,所以返回值类型写操作数所对应的类类型的引用,形参也应该写操作数所对应的类类型的引用。
3.为了预防自己给自己赋值,造成不必要的消耗,我们用if语句检查,发现是自己给自己赋值,就直接返回。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void print(){cout << _year << " " << _month << ' ' << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d(2024, 4, 21);Date d1;d1 = d; //d1.operator=(d); 也可以这么写d.print();return 0;
}
4.赋值运算符只能重载成类的成员函数,不能重载成全局函数
原因:如果在类内不显式定义赋值运算符重载函数,那么编译器会生成默认的赋值运算符重载函数,会产生冲突。
这种冲突实际上是可以避免的,但是重载的赋值运算符的两个操作数应该是两个来自同一个类的对象, 如果写成别的就没什么意义的,并不会出现这样的场景,编译器干脆禁止了这种行为!
5.用户没有显式定义时,编译器会生成一个默认的赋值运算符重载函数,以值的方式逐字节拷贝(类似于默认的拷贝构造函数),内置类型成员变量直接赋值,而自定义类型的成员变量需要调用对应类的赋值运算符重载完成赋值。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;//Date d2 = d1; 注意:不能这么写,这是拷贝构造!!!Date d2;d1 = d2;return 0;
}
编译器默认生成的赋值运算符重载函数可以进行值拷贝,一般情况下,只需要值拷贝的场景下,可以直接用默认的。
但是,涉及到深拷贝就不行了。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class int_arr
{
public:int* arr = (int*)malloc(sizeof(int));
};
int main()
{int_arr arr1, arr2;arr1 = arr2;arr1.arr[0] = 5;cout << arr2.arr[0];return 0;
}
这种情况下需要深拷贝,浅拷贝是不可以的,例如上面的代码,改变对象arr1中的数据也改了arr2中的数据,这不是我们想看到的。
注意:如果类中涉及资源管理,一定不能用浅拷贝。
1.3前置++和后置++
前置++和后置++都是单目运算符,而且两操作符写法一样,需要采用特殊方法区分。
不做处理的就是前置++的重载
在小括号里加int的是后置++的重载
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器//自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存//一份,然后给this + 1//而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};
int main()
{Date d;Date d1(2022, 1, 13);d = d1++; // d: 2022,1,13 d1:2022,1,14d = ++d1; // d: 2022,1,15 d1:2022,1,15return 0;
}
2.const成员函数
我们知道,成员函数的第一个参数是看不见的this,它的类型是T*const(这里的T是该成员所对应的类的名字),那么,有没有什么办法在T前面加个const修饰,是的this的类型变为const T*const,从而使得this指向的内容无法改变(通过this访问)?
解答:在成员函数的括号的右边加const
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year = 0;int _month = 0;int _day = 0;
public:void print()const{_year = 2024;cout << _year << ' ' << _month << ' ' << _day << endl;}
};
int main()
{Date d;d.print();return 0;
}
这里报错了,说明达到效果了。
3.取地址操作符及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器默认生成的就够了。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date* operator&(){return this;}const Date* operator&()const{return this;}
};
int main()
{Date d;cout << &d << endl;const Date d1;cout << &d1;return 0;
}
只有特殊情况,才需要重载,比如想让别人取地址时获取其他指定地址。