一.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)。
假如我们需要实现一个日期加上一个天数得到一个新的日期,此时按照我们以前的思考方式,是实现一个函数,但是利用运算符重载,会变得更加直观明了
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void show() { cout << _year << "/" << _month << "/" << _day << endl; } int _year; int _month; int _day; }; //函数判断日期相等 bool DateEqual(const Date& d1, const Date& d2) { return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; } //运算符重载的定义 使用关键字 operator 【运算符】 bool operator==(const Date& d1 ,const Date& d2) { return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; } int main() { Date d2(2022, 4, 23); Date d1(2022, 1, 13); //函数使用 if (DateEqual(d1, d2)) cout << "true" << endl; else cout << "false" << endl; //运算符重载的使用 if (d1 == d2) cout << "true" << endl; else cout << "false" << endl; return 0; }
但是一般我们的成员变量都是私有的,如果是私有的我们就不能直接访问,我们通过添加 get函数来解决,或者通过将运算符重载的函数写道类的里面,变成成员函数,这里我们就直接封装到类里面:
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } // bool operator==(Date* this, const Date& date) // 这里需要注意的是,左操作数是this,指向调用函数的对象 bool operator ==(const Date& date) { return _year == date._year && _month == date._month && _day == date._day; } void show() { cout << _year << "/" << _month << "/" << _day << endl; } int _year; int _month; int _day; }; int main() { Date d2(2022, 1, 13); Date d1(2022, 1, 13); //运算符重载的使用 if (d1 == d2) cout << "true" << endl; else cout << "false" << endl; return 0; }
注意:在将运算符重载写成成员成员函数时,重载运算符只需要写一个参数即可,因为有一个参数就是本对象本身,就是隐藏在成员函数列表的this指针。
二.赋值运算符重载格式:
1.参数类型:const T&,传递引用可以提高传参效率
2.返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值
3.返回*this :要复合连续赋值的含义
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) { //如果不是自身赋值,即d1=d1,才需要成员变量对用赋值。 if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } void show() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date date1; Date date2(2023, 2, 7); date1.show(); //因为有返回值就可以实现链式访问 (date1 = date2).show(); return 0; }
4. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date{ public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } int _year; int _month; int _day; }; //赋值运算符重载成全局函数,注意重载成全局函数时没有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; }
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。
5.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
class Time { public: Time() { _hour = 1; _minute = 1; _second = 1; } Time& operator=(const Time& t) { if (this != &t) { cout << "Time:operator=" << endl; _hour = t._hour; _minute = t._minute; _second = t._second; } return *this; } private: int _hour; int _minute; int _second; }; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void show() { cout << _year << "/" << _month << "/" << _day << endl; } private: // 基本类型(内置类型) int _year ; int _month; int _day; // 自定义类型 Time _t; }; int main() { Date d1; d1.show(); Date d2(2023,2,7); d1 = d2; d1.show(); return 0; }
注意:内置类型成员变量是直接赋值的,而自定义类型成员变.量需要调用对应类的赋值运算符
重载完成赋。
但是对于有空间申请的空间是需要,我们不能光靠编译器自己生成的赋值运算符重载的:
class A { public: A() { a = 10; arr = (int*)malloc(sizeof(int) * a); for (int i = 0; i < a; i++) { arr[i] = i; } } void show() { for (int i = 0; i < a; i++) { cout << arr[i] << " "; } cout << endl; } int* arr; int a; }; int main() { A a; A b; a.show(); b.show(); b = a; //修改b对象的arr数组 for (int i = 0; i < b.a; i++) { b.arr[i] = 0; } //观察两个对象中arr的元素 b.show(); a.show(); return 0; }
我们只是修改了b对象的 arr 数组,但是a对象的arr数组也被跟着修改了。
具体的情况是:
三.前置++和后置++重载
我们知道前置++和后置++唯一的区别就是,返回值不同。前置++返回++之后的结果,前置++返回++之后的结果。那么运算符重载以后也应该支持前置++和后置++的特性。
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; } void show() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 10, 1); d1.show(); Date d2 = d1++; d2.show(); d1.show(); d2 = ++d1; d1.show(); d2.show(); return 0; }
四.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
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 ConstPrint() 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; // 日 }; int main() { Date d1(2022, 1, 13); d1.Print(); const Date d2(2022, 1, 13); d2.ConstPrint(); return 0; }
思考下面的几个问题:
1. const对象可以调用非const成员函数吗?
解答:const对象不可以调用非const成员函数,因为非const成员函数的*this也是没有经过const修饰的,如果调用了const成员函数,就相当于权限放大。
2. 非const对象可以调用const成员函数吗?
解答:非const对象可以调用const成员函数,因为由const修饰的成员函数实际上修饰的就是*this,相当于权限缩小,是允许的。
3. const成员函数内可以调用其它的非const成员函数吗?
解答:const成员函数不可以调用其他的非const成员函数,也是一个权限放大的例子。
4. 非const成员函数内可以调用其它的const成员函数吗?
解答:非const成员函数可以调用其他的const成员函数,也是一个权限缩小的例子。
五.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date { public: Date* operator&() { return this; } const Date* operator&()const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date date; cout << &date << endl; return 0; }
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } Date* operator&() { return (Date*)0x00000001; } const Date* operator&()const { return (Date*)0x0000250; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { const Date date(2022,10,1); Date date1(date); cout << &date << endl; cout << &date1 << endl; return 0; }
六. cout 输出类信息
cout是类 ostream 的对象,表示面向窄字符(字符类型)的标准输出流。它对应于 C 流标准输出。
作为 ostream 类的对象,可以使用插入运算符(运算符<<)将字符作为格式化数据写入其中,也可以使用成员函数(如 write)将字符作为无格式数据写入其中。
对象在带有外部链接和静态持续时间的头文件声明<iostream>:它持续整个程序的持续时间。
我们可以将ostream的对象cout,也作为运算符重载的参数:
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //<<运算符重载 void operator<<(ostream& out) { out << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date date(2023, 2, 8); //按照运算符左边到右边是参数从左往右对应 //成员函数第一个参数就是this指针,所以<<运算符左边就是date对象 date << cout; //等价于 date.operator<<(cout); return 0; }
但是 date<<cout ,非常不符合我们的使用习惯,有没有一种办法可以实现 cout << date 呢?造成这种情况的原因是成员函数的第一个参数默认是 this 指针,如果我们不将 << 设计为成员函数是不是就可以解决这个问题呢?
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; }; //<<运算符重载 void operator<<(ostream& out, Date& d) { out << d._year << "/" << d._month << "/" << d._day << endl; } int main() { Date date(2023, 2, 8); //按照运算符左右参数,分别对应<<运算符重载从左往右的参数 cout << date; //等价于 operator<<(cout, date); return 0; }
这就又有一个新的问题,成员变量私有化,不允许外部函数直接访问,面对这个问题,我们有两种解决方案:
1.设置get函数
设计成员函数用于返回私有成员变量的值,供外部读取。
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //Get函数,用于外部读取私有成员变量 int& Get_year() { return _year; } int& Get_month() { return _month; } int& Get_day() { return _day; } private: int _year; int _month; int _day; }; //<<运算符重载,为了支持连续访问,加上返回值 otsream& operator<<(ostream& out, Date& d) { out << d.Get_year() << "/" << d.Get_month() << "/" << d.Get_day() << endl; return out; } int main() { Date date(2023, 2, 8); //按照运算符左右参数,分别对应<<运算符重载从左往右的参数 cout << date; //等价于 operator<<(cout, date); return 0; }
2.设计友元函数
只需在将函数声明写在Date类的内部,并在函数的前面加上关键字 friend,就可以和类建立友好关系,友元函数可以像成员函数一样随意访问成员变量。
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //友元函数 friend void operator<<(ostream& out, Date& d); private: int _year; int _month; int _day; }; //<<运算符重载 void operator<<(ostream& out, Date& d) { out << d._year << "/" << d._month << "/" << d._day << endl; } int main() { Date date(2023, 2, 8); //按照运算符左右参数,分别对应<<运算符重载从左往右的参数 cout << date; //等价于 operator<<(cout, date); return 0; }
七.Date类实现
前面一直在用Date类举例子,这次我们来全面实现一下,练习我们之前学过的类与对象的姿势。
我们严格按照开发标准来实现Date类:
(1)Date类的主要功能模块
//Date.h文件 #include<iostream> #include<assert.h> using namespace std; class Date { friend ostream& operator<<(ostream& out, Date& d); public: Date(int year, int month, int day ); //日期比较大小 bool operator>(const Date& date); bool operator >= (const Date& date); bool operator <(const Date& date); bool operator<=(const Date& date); bool operator ==(const Date& date); //日期+天数 Date operator+(int x); //日期+=天数 Date& operator+=(int x); //日期-日期=天数 int operator-(const Date& date); //日期-天数=日期 Date operator-(int x); //日期-=天数 Date operator-=(int x); //Date++ Date operator++(int); //++Date Date& operator++(); private: int GetMonthDay(int year, int month);//获取每月天数 int _year;//年 int _month;//月 int _day;//日 }; //Date.cpp //构造函数定义 Date::Date(int year , int month, int day) { assert(month <= 12 && day <= this->GetMonthDay(year, month)); _year = year; _month = month; _day = day; } //输出日期:年/月/日(友元函数) void operator<<(ostream& out, Date& d) { out << d._year << "/" << d._month << "/" << d._day << endl; }
(2)日期大小比较
//Date.cpp bool Date::operator>(const Date& date) { if (_year != date._year)//如果年不相等,直接判断出结果 return _year > date._year; if (_month != date._month)//年相等的时候,如果月不相等,直接判断出结果 return _month > date._month; if (_day != date._day)//年.月相等的时候,如果日不相等,直接判断出结果 return _day > date._day; } bool Date::operator >= (const Date& date) { //>=就是或上一个相等 return *this > date || *this == date; } bool Date::operator ==(const Date& date) { //相等需要年月日都相等 return _year == date._year && _month == date._month && _day == date._day; } bool Date::operator <(const Date& date) { // >=取反 return !(*this >= date); } bool Date::operator<=(const Date& date) { //<或上== return *this < date || *this == date; }
(3)日期+天数,日期+=天数,++日期,日期++
int Date::GetMonthDay(int year, int month) { int montharr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)&&month==2) { return 29; } return montharr[month]; } Date Date::operator+(int x) { Date tmp(*this); if (x < 0) { tmp -= -x; return tmp; } tmp += x; return tmp; } //先将天数全部加上去,然后依次减去当月天数,并判断年月的变换。 Date& Date::operator+=(int x) { if (x < 0) { *this -= -x; return *this; } _day += x; while (_day > GetMonthDay(_year,_month)) { _day -= this->GetMonthDay(_year,_month); _month++; if (_month > 12) { _year++; _month = 1; } } return *this; } //Date++(后置++) Date Date::operator++(int) { Date temp(*this); *this+=1; return temp; } //++Date(前置++) Date& Date::operator++() { *this += 1; return *this; }
(4)日期-日期,日期-天数,日期-=天数
//计数计算,一天一天加,直到日期相等。 int Date::operator-(const Date& date) { int count = 0; Date bigdate = *this; Date smalldate = date; if (bigdate < smalldate) { Date tmp = bigdate; bigdate = smalldate; smalldate = tmp; } while (!(smalldate == bigdate)) { smalldate++; count++; } return count; } //日期-天数,先算整数月的减法,直到剩下的天数,小于本月天数, //加上第一个月,和最后一个月的剩下的天数计算 Date Date::operator-(int x) { Date tmp(*this); if (x < 0)//如果天数小于0,就相当于加上天数 { tmp += -x; return tmp; } tmp._month--; if (tmp._month == 0) { tmp._month = 12; tmp._year--; } tmp._day = tmp.GetMonthDay(tmp._year, tmp._month); while (x > tmp.GetMonthDay(tmp._year, tmp._month)) { x -= tmp.GetMonthDay(tmp._year, tmp._month); tmp._month--; if (tmp._month == 0) { tmp._month = 12; tmp._year--; } tmp._day = tmp.GetMonthDay(tmp._year, tmp._month); } tmp._day -= x; return tmp + this->_day; } //不仅计算返回,而且修改当前对象 Date Date::operator-=(int x) { if (x < 0) { *this += -x; return *this; } *this = *this - x; return *this; }
测试