【C++】运算符重载
引言
内置类型可以直接用运算符,简单并且可以直接转为对应的指令。
而自定义类型不可以直接用,没有对应的指令。
例:比较int 简单,我们知道2>1,
而比较Date 对象,单纯用<>=符号,我们不知道该怎样进行比较。
所以,需要我们自行规定怎样去进行比较,这就是运算符重载。
实质
运算符重载的实质是具有特殊名字的函数。
格式:返回类型 operator+运算符 (参数列表){函数体}
说明:
- 同样具有返回类型(与运算符操作结果保持一致)、参数列表和函数体。
- 参数个数和运算符作用的运算对象数量一样多。
例:比较Date对象。先将运算符重载函数定义在全局。
注意:
参数列表用 const 引用对象。避免操作失误导致对象被意外改变。避免权限被放大。
将运算符重载定义在全局,而对于全局函数,若要访问的成员变量被类 私有化,出现问题。
有以下几种解决方案:
- 成员放公有
- 提供Getxxx函数(Java常用)
- 友元函数
- 直接将该函数定义到类里面做成员函数。注意此时参数列表中隐含了 this 作为参数之一。
class Date { public: Date(int year = 1, int month =1, int day =1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << '年' << _month << "月" << _day << "日" << endl; } //重载为成员函数,解决访问问题。 //这里只需要再传入一个参数即可,默认第一个参数为this bool operator == (const Date& d1) { return _year == d1._year && _month == d1._month && _day == d1._day; } private: int _year; int _month; int _day; };调用方式
函数调用有两种形式。
Date d1(2026, 6, 13); Date d2(2026, 6, 13); //运算符重载的两种调用方式。 d1 == d2; //这种比较常用。 d1.operator==(d2); //注意优先级,这里<<优先级高于==,所以加() cout << (d1 == d2) << endl;如何设计
要有意义。
例:
1.日期减天数、日期减日期,减号(-) 重载具有意义。
Date& Date::operator-=(int day) { if (day < 0) //用户可能输入负数。调用+=来实现。 { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; } //先将月份借位了,再计算_day. _day += GetMonthDay(_year,_month); } return *this; } //但是,这种实现逻辑,结束后原日期也被更改。不是我们要的结果。 //要让原日期不跟着改变,用拷贝构造一个辅助日期来进行操作。 Date Date::operator-(int day)const { Date tmp(*this); //为了拿到当前的调用对象,拷贝构造传*this进去。 tmp -= day; // 调用-= 函数来复用。优化了代码。 return tmp; }//两日期相减 *this-d=? int Date::operator-(const Date& d)const { Date max = *this; Date min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int count = 0; while (max != min) //++代码复用,min每次+1,直到==max, { //加了多少次就差了多少天。 ++min; ++count; } return count * flag; }2.日期相加无意义。但是日期加xx天,有意义。故+号可以重载。
Date& Date::operator+=(int day) { if (day < 0) //用户可能输入负数,调用-=来实现。 { return *this -= -day; } _day += day; while (_day > GetMonthDay(_year,_month)) { _day -= GetMonthDay(_year, _month); ++_month; //使用前置--,减少拷贝。 if (_month == 13) { _month = 1; ++_year; } } return *this; } Date Date::operator+(int day)const { Date tmp(*this); tmp += day; return tmp; }实现逻辑:如何获取某一月的总天数。统一用GetMonthDay函数实现
(因为是频繁调用的小函数,考虑用内联)
//Date类成员函数 //调用频繁且规模小,考虑设为内联函数,声明和定义不可分离。 int GetMonthDay(int year,int month) const { assert(month > 0 && month < 13); //设置为静态数组,不用每次调用都开辟新空间,创建一样的数组。 //用数组下标表示月份,数组对应的值表示该月的总天数。 static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; //判断先判断简单的month==2,效率高一点 if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } else { return monthDayArray[month]; } }tips:能用引用返回就用引用返回,减少拷贝。判断函数结束后,返回的值还存不存在,存在就能引用返回。
注意
- 重载的运算符必须是语法中有的符号,不能自己创建新的。
- 重载操作符至少有一个类类型参数。不作用于完全的内置类型间。
//编译报错:"operator +"必须至少有一个类类型的形参 int operator+(int x, int y) { return x - y; } - (.* :: sizeof ?: . )这5个运算符不可重载。(*可以重载)
解释(.*) :了解即可
成员函数指针
成员函数要取地址,前面得加 & 取地址符号。
成员函数指针和成员函数在声明时也得指定类域。
成员函数指针调用,需要用对象进行调用,此时 (aa.*func)();
class A { public: void func() { cout << "A::func()" << endl; } }; typedef void(A::* PF)();//成员函数指针类型//C++中规定成员函数要加&才能取到函数指针; PF pf = &A::func; A obj;//定义ob类对象temp //对象调用成员函数指针时,使用.*运算符 (obj.*pf)();
4. 函数重载和运算符重载,重载的意义不同。两个运算符重载可以构成函数重载。
例如:++有前置、后置之分。为了区分,规定对于后置++,参数列表增加一个int。
与前置构成重载,进行区分。但我们并不会真正使用int,只是让编译器能够区分出两种++,自动进行类型匹配 。
//类成员函数中定义。 //前置++,先++操作,再返回值 Date& operator++() { _day += 1; return *this; } //后置++,先返回值,再++操作 Date operator++(int) { Date tmp(*this); _day += 1; return tmp; }Date d1(2026, 6, 13); Date d2(2026, 6, 13); Date d3 = ++d1; d1.Print(); d3.Print(); Date d4 = d2++; d2.Print(); d4.Print();前置++性能更好,因为是引用返回,减少了拷贝,而后置需要进行2次拷贝。
赋值 运算符重载
同构造、析构、拷贝构造函数一样,也是一个默认的成员函数。
与拷贝构造进行区分
(因为都要用到 = 号进行操作,容易混淆)
- 相同点:它们都进行对象的拷贝。
- 不同点:拷贝构造核心是用一个已存在的对象去初始化一个需要实例化的对象(构造),
而赋值运算符重载是针对已经存在的两对象之间的拷贝。
特点
- 是一个运算符重载,但规定必须重载为成员函数。参数建议写成const 引用,减少拷贝。
- 要设置返回值,才可以连续赋值。若返回值还存在,建议用引用返回,减少拷贝。
- 无显式实现时,编译器会自动生成。与拷贝构造一致,对内置类型成员变量完成值拷贝/浅拷贝(一个字节一个字节拷贝)对自定义类型成员变量,调用他的赋值运算符重载。(参考拷贝构造,深浅拷贝)显式析构、显式拷贝构造、显式赋值运算符重载,应该是同时出现的。
Date(const Date& d) //拷贝构造,参数建议写成const引用 { cout << "Date(const Date& d)" << endl; _year = d._day; _month = d._month; _day = d._day; } Date& operator=(const Date& d) { cout << "Date& operator=(const Date& d)" << endl; //用对象的地址检查:自己给自己赋值的情况。 if (this != &d) { _year = d._day; _month = d._month; _day = d._day; } //d1 = d2表达式的返回对象应该为d1,也就是*this return* this; }Date d1(2026, 6, 13); Date d2(d1); //调用拷贝构造,将d1拷贝给d3 Date d3 = d1; Date d4(2026,6,14); d1 = d4; //调用赋值运算符重载流插入/提取 运算符重载
是什么?
依托函数重载可以对不同类型操作打印和输入。
C中的print和scanf无论怎样设置,都无法对自定义类型操作,
C++提出流插入/提取重载,就可以对自定义类型进行操作。
说明:cout是 ostream类型 的对象。cin是 istream类型 的对象。
ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; }istream& operator>>(istream& in, Date& d) { cout << "请依次输入年月日:>" << endl; in >> d._year >> d._month >> d._day; while(!d.CheckDate()) { cout << "请重新输入:" << endl; in >> d._year >> d._month >> d._day; } return in; }注意
但凡重载为成员函数,就会自动把this置于第一个形参位置。即this在前,cout在后。
用 cout<<d ;调用时,因与形参的顺序不一致,出错。
所以,不做成员函数,而做全局函数。重置形参顺序。但是,无法访问到类中私有的成员变量。
解决:在类中声明为友元函数。全局函数声明和友元函数声明都得来一遍,因为它们的域不同。
cin也是一样的逻辑。
const成员函数
为什么要用?
//成员函数 void Print(); //编译出错,Print函数中第一个形参是Date* this //将const Date传入,导致权限放大。 void TestDate() { const Date d1(2026, 4, 14); d1.Print(); }解决:使用const修饰成员函数,形参会被设置为const Date 权限平级了,可以使用。
格式
void Print() const;建议
凡是不改变调用对象的函数都建议用const修饰。
//非const对象也可以调用const成员函数,权限可以缩小 Date d1(2026,6,14); d1.Print(); const Date d2(2026,4,5); d2.Print();取地址运算符重载
分为:普通取地址运算符重载和const取地址运算符重载。它们构成函数重载。
一般这两个函数编译器自动生成的已经够用 了,我们不需要显式实现。
class Date { public: Date* operator&() { return this; //return nullptr; } const Date* operator&() const { return this; //return nullptr; } private: int _year; int _month; int _day; };