本篇文章来介绍一下C++中的运算符重载,以及与运算符重载有关的三个默认默认成员函数:赋值运算符重载,普通对象取地址与const对象取地址操作符重载,也就是下面图片中6个默认成员函数的后三个,前三个默认成员函数在之前文章中已经讲过
类和对象:构造函数,析构函数与拷贝构造函数_一棵西兰花的博客-CSDN博客,
大家不太清楚的可以去看一下。
本篇文章主要通过一个日期类来进行讲解。我会把完整的日期类放到后面。
class Date { public: //... private: int _year; int _month; int _day; };
1.什么是运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针
- .* :: sizeof ? : . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
光看定义是不行的,我们实现以下 > 和 == 的重载
// >运算符重载 //因为>号有两个操作数,因为有一个是隐藏的this指针,所以这里只有一个参数 bool Date::operator>(const Date& d) { if (_year < d._year) { return false; } else if (_year == d._year && _month < d._month) { return false; } else if (_year == d._year && _month == d._month && _day <= d._day) { return false; } else { return true; } } // ==运算符重载 bool Date::operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; }
有一个 > 和 == 或< 和 == ,其他比较运算符就可以有这两个推导出来,不用再一个一个写。
2.赋值运算符重载
1.赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率,加const是为了防止参数被修改
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要符合连续赋值的含义
代码:
Date& Date::operator=(const Date& d)//可以不用引用,但没有必要 { if (this != &d)//自己给自己赋值 { _year = d._year; _month = d._month; _day = d._day; } return *this; }
对于返回*this,因为我们平时的赋值运算符可以实现 类似与 a = b = c = d 的方式,显示调用就是a.operator(b.operator(c.operator(d))),如果operator=函数没有返回值,就会出现错误。
2.赋值运算符只能重载成类的成员函数不能重载成全局函数
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数 Date& operator=(Date& left, const Date& right) { if (&left != &right) { left._year = right._year; left._month = right._month; left._day = right._day; } return left; } // 编译失败: // error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。因为他会将地址也进行简单的值拷贝(浅拷贝),在进行析构时回释放两次,会导致程序崩溃。
3.前置++与后置++重载
前置++与后置++的操作数只有一个,而且只有返回值不同,一个前置++返回自己加一,后置++需要在构造一个Date 来存放没有加一时的结果进行返回。只有返回值不同,没有办法区分两个函数,只好在后置++加入一个占位参数,没有什么用途,只是为了区分前置与后置,编译器也是通过这个方式来识别。
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
代码:
// 前置++ Date& Date::operator++() { *this += 1; return *this; } // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值, //故需在实现时需要先将this保存一份,然后给this+1 // 而temp是临时对象,因此只能以值的方式返回,不能返回引用 // 后置++ 传入的值不用接收 Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; }
-- 也一样:
// 后置-- Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; } // 前置-- Date& Date::operator--() { *this -= 1; return *this; }
4.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,这里的const需要加在形参列表的后面,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
我们来看看下面的代码:
这里类成员函数Print() 没有用const修饰。
const Date d1(2023, 9, 23); //const修饰的对象调用不了成员函数,因为权限放大 d1.Print();//d1.print(&d1) 隐藏的传入this的指针是const Data* 类型
Print() 没有被const 修饰,函数形参this指针是 普通的 Date* 类型,而被const修饰的对象调用Print()函数,传入的this指针是const Data* 类型 ,会发生权限的放大,权限可以缩小,平移,但是不能放大,会报错。
请思考下面的几个问题:
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
答案:1,3不可以,权限放大。2,4可以,权限缩小。
我们可以通过函数重载使不同权限的对象调用不同的函数。如下面代码的Print:
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout << "Print()" << endl; cout << "year:" << _year << endl; cout << "month:" << _month << endl; cout << "day:" << _day << endl << endl; } void Print() const { cout << "Print()const" << endl; cout << "year:" << _year << endl; cout << "month:" << _month << endl; cout << "day:" << _day << endl << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; void Test() { Date d1(2022,1,13); d1.Print(); const Date d2(2022,1,13); d2.Print(); }
当然Print函数用const 修饰是没有什么意义的。我们看下面的取地址及const取地址操作符重载。
5.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
class Date { public : //这种方式返回指针的内容可以修改,可读可写 Date* operator&() { return this ; } //这种方式返回指针的内容也不能修改,只读 const Date* operator&()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,不返回this,返回nullptr等等。
6. Date 类的实现
Date.h
class Date { public: // 获取某年某月的天数 int GetMonthDay(int year, int month); //全缺省的构造函数 Date(int year = 1900, int month = 1, int day = 1); // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d); // 日期+=天数 Date& operator+=(int day); // 日期+天数 Date operator+(int day); // 日期-天数 Date operator-(int day); // 日期-=天数 Date& operator-=(int day); void Print(); //下面两个是运算符重载,也构成函数重载 // 前置++ //++d1 -> d1.operator++() Date& operator++(); // 后置++ //d1++ -> d1.operator++(0)//传一个整形即可,只是用来区分前置++ //加了一个int参数,进行占位,跟前置++构成函数重载进行区分 //本质后置++调用,编译器进行了特殊处理 Date operator++(int);//传入的值不用接收 // 后置-- Date operator--(int); // 前置-- Date& operator--(); // >运算符重载 bool operator>(const Date& d); // ==运算符重载 bool operator==(const Date& d); // >=运算符重载 bool operator >= (const Date& d); // <运算符重载 bool operator < (const Date& d); // <=运算符重载 bool operator <= (const Date& d); // !=运算符重载 bool operator != (const Date& d); // 日期-日期 返回天数 int operator-(const Date& d); const Date* operator&()const; Date* operator&(); private: int _year; int _month; int _day; };
Date.cpp
// 获取某年某月的天数 int Date::GetMonthDay(int year, int month) { static int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)) { return 29; } return MonthDay[month]; } //全缺省的构造函数 //声明和定义缺省参数的值,不能都给,必须在声明中给, //因为在编译时头文件中看到的是声明,而声明中没有提供缺省值,会报错。到不了后面的链接,通过函数名修饰规则来找到函数的定义 Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; //检查日期日期是否合法 if (month < 1 || month>12 || day < 1||_day > GetMonthDay(_year, _month)) { cout << "非法日期\n" << endl; exit(-1); } } // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& Date::operator=(const Date& d)//可以不用引用,但没有必要 { if (this != &d)//自己给自己赋值 { _year = d._year; _month = d._month; _day = d._day; } return *this; } // 日期+=天数 Date& Date::operator+=(int day)//0次拷贝 0次赋值 { if (day < 0) { return *this -= (-day); } _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; while (_month > 12) { _month = 1; _year++; } } return *this; } // 日期+天数 Date Date::operator+(int day)//两次拷贝 { if (day < 0) { return *this - (-day); } Date ret(*this); ret += day; return ret; } // 日期-天数 Date Date::operator-(int day) { if (day < 0) { return *this + (-day); } Date ret = *this; ret -= day; return ret; } // 日期-=天数 Date& Date::operator-=(int day) { if (day < 0) { return *this += (-day); } _day -= day; while (_day < 1) { _month--; while (_month < 1) { _month = 12; _year--; } _day += GetMonthDay(_year, _month); } return *this; } //只读函数可以加const,内部不涉及修改成员 //void Date::Print(const Data* this) //void Date::Print() //可以同时存在,因为this指针类型不同,如果只有const版本也可调用,因为权限可以缩小 //{//不过在这里没有意义 // cout << _year << "/" << _month << "/" << _day << endl; //} void Date::Print() const { cout << _year << "/" << _month << "/" << _day << endl; } // 前置++ Date& Date::operator++() { *this += 1; return *this; } // 后置++ Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; } // 后置-- Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; } // 前置-- Date& Date::operator--() { *this -= 1; return *this; } // >运算符重载 bool Date::operator>(const Date& d) { if (_year < d._year) { return false; } else if (_year == d._year && _month < d._month) { return false; } else if (_year == d._year && _month == d._month && _day <= d._day) { return false; } else { return true; } } // ==运算符重载 bool Date::operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } // >=运算符重载 bool Date::operator >= (const Date& d) { return *this > d || *this == d; } // <运算符重载 bool Date::operator < (const Date& d) { return !(*this >= d); } // <=运算符重载 bool Date::operator <= (const Date& d) { return !(*this > d); } // !=运算符重载 bool Date::operator != (const Date& d) { return !(*this == d); } //跟日期-天数构成函数重载 // 日期-日期 返回天数 int Date::operator-(const Date& d) { Date max = *this; Date min = d; int flag = 1; if (max < min) { min = *this; max = d; flag = -1; } int count = 0; while (max != min) { --max; count++; } return count * flag; } //日常自动生成的就可以 //不要被取到有效地址 //const Date* Date::operator&()const //{ // return nullptr; //} const Date* Date::operator&()const { return this; } //这个接口是读写 Date* Date::operator&()//函数重载,一个给const对象用,一个给非const对象用 { return this; }
本篇结束!