像+、-、*、/ … 等操作符默认支持内置类型(如 int、double、long…)的运算,但是对于自定义类型如果也想用这些操作符该怎么办呢?
以下面的 Date 类为例:
class Date {
public:
Date(int year = 2022, int month = 8, int day = 24)
:_year(year)
, _month(month)
, _day(day)
{}
//析构函数和拷贝构造函数编译器默认生成的就可以
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1;
d1 += 3;
return 0;
}
假如我想给 d1 加上三天,d1 加上后的结果表示的日期为 2022-8-27。
默认的 += 完不成这个任务,而想要实现这一点,c++则引入了运算符重载。
运算符重载是具有特殊函数名的函数,它可以定义成类的成员函数,也可以定义成全局的。
函数名字为:关键字后面接需要重载的运算符符号(比如operator+=)
函数原型:返回类型 + operator运算符 + 参数列表
注意事项:
那么简单实现一下上面的 += 运算符重载:
写成成员函数的形式:
Date& operator+=(int day) {
_day += day;
return *this;
}
写成全局的形式:
Date& operator+=(Date& d, int day) {
d._day += day;
return d;
}
注意上面只是极为粗略的实现了一下,进位什么的都没有考虑。
对于这个运算符重载有几个点需要注意一下:
friend Date& operator+=(Date& d, int day);。类还有一个特殊的成员函数——赋值运算符重载函数。
字面意思,这个函数实现的功能就是用一个对象给另一个对象赋值。
注意:
上面又提到了浅拷贝,相应的又要涉及到深拷贝。
其实像 Date 类这种简单的类就是要完成浅拷贝,不需要我们自己写,而一旦涉及到深拷贝就需要我们自己完成了。
下面以模拟实现 string 类为例,讲解一下赋值运算符重载函数的深拷贝怎么写。
类的部分声明如下:
class string {
public:
string(const char* str = "") {
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
~string() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
int _Size;
int _capacity;
}
赋值运算符重载的深拷贝有两种写法。
一种是我们可以先把原来的资源先清理掉,然后拷贝另一个对象的所有成员变量,实现起来就是下面这样:
string& operator=(const string& s) {
// 判断是否是自己给自己赋值
if (&s != this) {
delete[] _str;
_size = strlen(str);
_capacity = _size;
_str = new char[_size + 1];
// 这里不用 strcpy 的原因是我们不知道 s 内部会不会有 '\0'
strncpy(_str, s, _size);
}
return *this;
}
这是较为传统的写法。
不过都什么年代了,还在写传统深拷贝。
下面提供更简洁的一种写法:
string& operator=(string s) {
// c++库提供了一个全局的swap函数,可以按字节序实现两个变量的交换
// swap前面加::表明这是调用全局的swap,防止与类内部可能定义的swap函数冲突
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
return *this;
}
这就不多做解释了,一目了然。
我们在使用输入输入输出函数时不知有没有注意到一点,我们并没有表示要输入或输出对象的类型,但编译器都能自动识别出来。
这实际上就调用了 << 和 >> 的运算符重载函数:


还是那句话,库里提供的只能实现内置类型的输入输出,对于自定义类型的输出还是需要我们自己写。
以 Date 类为例,我们写一下它的输入输出运算符重载。
类的简单声明:
class Date {
public:
Date(int year = 2022, int month = 8, int day = 24)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d;
cout << d << endl;
return 0;
}
首先明确一点,我们想要的是上面代码似的像输出内置类型一样输出自定义类型。
简单粗略的理解一下 << :
无论是输出自定义类型还是内置类型,都会去调用它的运算符重载函数 operator<<,它的返回类型为 ostream& ,所以可以实现连续输出。<< 是有两个操作数的,像我们使用最多的 cout ,它的类型就是 ostream,是 << 的第一个操作数,要输出的对象就是第二个操作数。
而一旦我们把它的运算符重载函数定义成成员函数,那么 this 指针就是第一个操作数,实际使用起来就会是
d1 << cout;这种形式。这并不是我们想要的。所以考虑到操作数的顺序问题,我们需要把它的运算符重载函数定义成全局的。而为了访问到类内部成员,还需要将它设置成友元函数。
不过这只是一种解决方案,如果是 string 类的话,我们还可以通过迭代器或**[]重载**的形式直接进行访问。
那么输出运算符重载就可以写成:
ostream& operator<< (ostream& out, Date& d) {
out << d._year << '-' << d.month << '-' << d.day;
return out;
}
相应的,输入运算符也可以写出来:
istream& operator>> (istream& in, Date& d) {
in >> d._year >> d.month >> d.day;
return in;
}