城阳区建设银行网站,重庆维力安网站建设,composer 发布wordpress,领域网站建设目录 前言一、运算符重载二、赋值运算符重载三、完善日期类3.1 重载关系运算符3.2 重载、3.3 重载-、-3.4 重载、--3.5 重载、 四、const成员五、取地址及const取地址操作符重载 前言
本文将以日期类为基础#xff0c;去探寻运算符重载的特性与使用方法… 目录 前言一、运算符重载二、赋值运算符重载三、完善日期类3.1 重载关系运算符3.2 重载、3.3 重载-、-3.4 重载、--3.5 重载、 四、const成员五、取地址及const取地址操作符重载 前言
本文将以日期类为基础去探寻运算符重载的特性与使用方法下面先给出日期类的基础定义
class Date
{
public:Date::Date(int year, int month, int day){if (month 0 month 12 day 0 day GetDay(year, month)){_year year;_month month;_day day;}else{cout 非法日期 endl;assert(false);}}
private:int _year;//年int _month;//月int _day;//日
};备注拷贝构造函数和析构函数均可以不写因为当前日期类的三个成员变量都是内置类型没有动态申请空间使用浅拷贝就可以。
一、运算符重载
如何比较两个日期的大小
int main()
{Date d1(2023, 7, 21);Date d2(2023, 6, 21);return 0;
}现如今定义了两个日期类的对象d1和d2该如何比较这两个对现象的大小呢首先想到的是写一个函数来比较他俩的大小向下面这样
//以小于比较为例
bool Less(const Date x, const Date y)
{if (x._year y._year){return false;}else if (x._year y._year x._month y._month){return false;}else if (x._year y._year x._month y._month x._day y._day){return false;}else{return true;}
}存在的问题首先这个函数是写在类外面的意味着日期类的成员变量如果是private私有的话在类外面就无法访问所以在这个函数里面是访问不到对象的年、月、日这三个成员变量即x._year等都是非法的要想实现该函数的功能日期类的成员变量必须是public公有。
其次在比较两个日期类对象大小的时候需要写成Less(d1, d2)这和我们平时直接用符号比较大小比起来不够直观。
为什么日期类不能直接使用 因为日期类是我们自己定义的属于一种自定义类型它的大小比较方式只有定义它的人知道而像int、double等内置类型是祖师爷创造C语言时就定好的祖师爷当然知道该如何比较两个内置类型变量的大小所以提前帮我们设置好了我们可以直接用去比较两个内置类型变量的大小而至于祖师爷是怎么设置的这里先埋一个伏笔。
运算符重载 为了解决上面Less函数存在的问题C引入了运算符重载它可以让我们直接使用来比较两个日期类的大小。
运算符重载是具有特殊函数名的函数也具有返回值类型函数名字、参数列表、返回值类型都和普通函数类似。
函数名字关键字operator后面接需要重载的运算符符号。函数原型返回值类型 operator操作符参数列表
bool operator(const Date x, const Date y)
{if (x._year y._year){return false;}else if (x._year y._year x._month y._month){return false;}else if (x._year y._year x._month y._month x._day y._day){return false;}else{return true;}
}上面就是对运算符的一个重载它的两个形参是Data类型的引用此时两个日期类对象就可以直接用来比较大小啦d1 d2本质上就是调用运算符重载函数但是由于上面的运算符重载函数还是写在类外面所以当日期类的成员变量是private私有的时候该运算符重载函数还是用不了。
//下面两条语句是等价的本质都是调用运算符重载函数
d1 d2;
operator(d1, d2);//d1 d2的本质将运算符重载函数写成成员函数 为了解决上面的私有成员变量在类外面无法访问的问题可以把运算符重载函数写成类的成员函数或者友元这样就能访问到私有的成员变量但是友元一般不建议使用因为友元会破坏封装。
bool operator(const Date d)
{if (_year d._year){return true;}else if (_year d._year _month d._month){return true;}else if (_year d._year _month d._month _day d._day){return true;}else{return false;}
}上面就是把运算符重载成类的成员函数此时参数只有一个因为是一个双目运算符类的非静态成员函数有一个隐藏的形参this指针所以形参就只需要一个。
//它们俩是等价的
d1 d2;
d1.operator(d2);//d1 d2的本质小Tips一个双目运算符如果重载成类的成员函数会把它的左操作数传给第一个形参把右操作数传给第二个形参。以上面为例this指针接收的是d1的地址d接收的是d2。
注意事项
不能通过连接其他符号来创建新的运算符比如operator。重载操作符必须有一个类类型参数。用于内置类型的运算符其含义不能改变例如内置的不能改变其含义。作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this。.*、::、sizeof、? :、.这五个运算符不能重载。
二、赋值运算符重载
区分赋值运算符重载和拷贝构造
Date d1(2020, 5, 21);
Date d2(2023, 6, 21);
d1 d2;//需要调用赋值运算符重载
Date d3 d1;//这里是调用拷贝构造函数
//Date d3(d1);//和上一行等价调用拷贝构造要区分赋值运算符重载和拷贝构造前者是针对两个已存在的对象将一个对象的值赋值给另一个而后者是用一个已存在的对象去初始化创建一个新对象。
赋值运算符重载格式
参数类型const TT是类型传引用返回可以提高效率。返回值类型T返回引用可以提高效率有返回值目的是为了支持连续赋值。检测是否自己给自己赋值。返回*this要符合连续赋值的含义。
Date operator(const Data d)
{if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;//出了作用域*this还在所以可以用引用返回
}只能是类的成员函数 上面的运算符最开始我们是在类外面把它重载成全局的后来为了保证类的封装性才把它重载成类的成员函数而赋值运算符天生只能重载成类的成员函数因为赋值运算符重载属于类的默认成员函数我们不写编译器会自动生成所以如果我们把赋值运算符重载写在类外面就会和编译器生成的默认赋值运算符重载发生冲突。
编译器生成的干了些什么工作 用户没有显式实现时编译器生成的默认赋值运算符重载对内置类型的成员变量是以值的方式逐字节进行拷贝浅拷贝对自定义类型的成员变量调用其对应类的赋值运算符重载。
三、完善日期类
有了上面的基础接下来完善一下日期类重载其他的运算符。
3.1 重载关系运算符
关系运算符有、、、、、!由于它们之间存在的逻辑关系可以通过复用来实现即要想知道a是否大于b可以通过判断a是否小于等于b来实现。因此我们只要写一个和的比较逻辑其他的直接复用即可。
重载
bool operator(const Date d)
{if (_year d._year){return true;}else if (_year d._year _month d._month){return true;}else if (_year d._year _month d._month _day d._day){return true;}else{return false;}
}重载
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);
}3.2 重载、
有时我们需要知道几天之后的日期比如我想知道100天后的日期此时就需要用当前的日期加上100但是一个日期类型和一个整型可以相加嘛答案是肯定的可以通过重载来实现。运算符重载只规定必须有一个类类型参数并没有说重载双目操作符必须要两个类型一样的参数。
获取某月的天数 日期加天数要实现日期的进位即当当前日期是这个月的最后一天时再加一天月份就要进一当当前的日期是12月31日时再加一天年份就要进一因此可以先实现一个函数用来获取当前月份的天数在每加一天后判断月份是否需要进位。
int GetDay(int year, int month)//获取某一月的天数
{static int arr[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 arr[month];
}除了2月每个月的天数都是固定的因此可以设置一个数组来存放每个月的天数并且以月份作为下标对应存储该月的天数这种方法类似于哈希映射。这里还有两个小细节第一个把数组设置成静态因为这个函数会重复调用多次把数组设置成静态它第一次创建之后一直到程序结束都还在可以避免函数调用时重复的创建数组。第二点把month 2放在前面判断因为只有当2月的时候才需要判断是否是闰年如果不是2月就不用判断是不是闰年。
重载
Date Date::operator(int x)
{if(x 0)//天数为负的时候{return *this - (-x);//复用-}/Date tmp *this;//Date tmp(*this);//和上面等价都是调用拷贝构造函数tmp._day _day x;while (tmp._day GetDay(tmp._year, tmp._month)){tmp._day tmp._day - GetDay(tmp._year, tmp._month);tmp._month;if (tmp._month 13){tmp._year;tmp._month 1;}}return tmp;//
}注意要计算ab的结果a是不能改变的因此一个日期加天数不能改变原本的日期也就是不能修改this指针指向的内容所以我们要先利用拷贝构造函数创建一个和*this一模一样的对象对应上面代码中的tmp在该对象的基础上去加天数。出了作用域tmp对象会销毁所以不能传引用返回。
重载 和很像区别在于是在原来是日期上进行修改即直接对this指针指向的日期做修改所以我们对上面的代码稍作修改就可以得到。
Date Date::operator(int x)
{if (x 0)//当天数为负{return *this - -x;//复用-}_day x;while (_day GetDay(_year, _month)){_day _day - GetDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}小Tips加一个负的天数就是算多少天以前的日期所以当天数为负的时候可以复用下面的-。
和之间的复用 可以发现和的实现方法十分相似那是否可以考虑复用呢答案是肯定的他俩其中的一方都可以去复用另一方。
去复用
Date Date::operator(int x)
{/Date tmp *this;//Date tmp(*this);//和上面等价都是调用拷贝构造函数tmp x;return tmp;//
}去复用
Date Date::operator(int x)
{*this *this x;//这里是调用赋值运算符重载return *this;
}注意上面的两种复用只能存在一个不能同时都去复用同时存在会出现你调用我我调用你的死穴。
既然只能存在一个那到底该让谁去复用呢答案是让去复用。因为原本的实现过程中并没有调用拷贝构造去创建新的对象而原本的实现过程中会去调用拷贝构造函数创建新的对象并且是以值传递的方式返回的期间又会调用拷贝构造。如果让去复用原本还无需调用拷贝构造复用后反而还要调用拷贝构造创建新对象造成了没必要的浪费。
3.3 重载-、-
有时我们也需要知道多少天以前的日期此时就需要重载-它的两个操作数分别是日期和天数其次我们有时还想知道两个日期之间隔了多少天这也需要重载-但此时的两个操作数都是日期。两个-重载构成了函数重载。
重载日期-天数 有了上面的经验我们可以先重载-再让-去复用-即可日期减天数就是要实现日期的借位。
Date Date::operator-(int x)
{Date tmp(*this);return tmp - x;//复用-
}重载-
Date operator-(int x)
{if (x 0)//天数天数小于0{return *this -x;//复用}_day - x;while (_day 0){_month--;if (_month 0){_month 12;_year--;}_day GetDay(_year, _month);}return *this;
}重载日期-日期 日期-日期它的形参是一个日期对象计算的结果是两个日期之间的天数所以返回值是int要像知道两个日期之间相隔的天数可以设置一个计数器让小日期一直加到大日期就可以知道两个日期之间相隔的天数。
int operator-(const Date d)
{Date max *this;//存放大日期Date min d;//存放小日期int flag 1;if (*this d){max d;min *this;flag -1;}int n 0;while (max ! min){--max;n;}return n * flag;
}3.4 重载、--
、--操作符无论前置还是后置都是一元运算符为了让前置和后置形成正确的重载C规定后置重载的时候多增加一个int类型的参数但是当使用后置调用运算符重载函数时该参数不用传递编译器自动传递。
重载前置
//前置,返回之后的值
Date Date::operator()
{return *this 1;//直接复用
}重载后置
//后置,返回加之前的值
Date Date::operator(int)//编译器会把有int的视为后置
{Date tmp(*this);*this 1;//复用return tmp;
}重载前置--
Date operator--()
{return *this - 1;//复用了-
}重载后置--
Date operator--(int)
{Date tmp(*this);*this - 1;//复用了-return tmp;
}对比前置和后置可以发现后置会调用两次拷贝构造函数一次在创建tmp的时候另一次在函数返回的时候。而前置则没有调用拷贝构造所以前置的效率相比后置会高那么一点。
3.5 重载、
同理对于自定义类型编译器仍然不知道如何打印所以要想通过去直接打印日期类对象需要我们对运算符进行重载。
重识cout、cin 我们在使用C进行输入输出的时候会用到cin和cout它们俩本质上都是对象cin是istream类实例化的对象cout是ostream类实例化的对象。 内置类型可以直接使用、本质上是因为库中进行运算符重载。而、不用像C语言的printf和scanf那样int对应%dfloat对应%f是因为运算符重载本质上是函数对这些不同的内置类型分别进行了封装在运算符重载的基础上又实现了函数重载所以、支持自动识别类型。 为什么不能重载成成员函数 要实现对日期类的要对进行重载。但是和其他的运算符有所不同上面重载的所有运算符为了保证类的封装性都重载成了类的成员函数但是不行因为我们平时的使用习惯是cout d1前面说过对于一个双目运算符的重载它的左操作数会传递给运算符重载函数的第一个形参右操作数会传递给运算符重载函数的第二个形参也就是说cout会传递给第一个形参日期类对象d2会传递给第二个形参如果运算符重载函数是类的成员函数的话那么它的第一个形参是默认的this指针该指针是日期类类型的指针和cout的类型不匹配当然也有解决办法那就是输出一个日期类对象的时候写成d1 cout此时就相当于d1.operator(cout)会把d1的地址传给this指针形参再用一个ostream类型的对象来接收cout即可但是这样的使用方式显然是不合常理的。
将重载成全局函数 正确的做法是把重载成全局函数此时函数形参就没有默认的this指针我们可以根据需要来设置形参的顺序第一个形参用ostream类对象来接收cout第二个形参用Date日期类对象来接收d1。
//重载成全局的
ostream operator (ostream out, const Date d)
{out d._year 年 d._month 月 d._day 日 endl;return out;
}注意形参out不能加const修饰因为我们就是要往out里面写东西加了const意味着out不能修改。其次为了实现连续的输出返回值是ostream类型的对象out因为此时出了作用域out还在所以可以用引用返回。
因为该运算符重载函数写在全局默认情况下在该函数内部是无法访问到日期类的私有成员变量为了解决这个问题可以把该运算符重载函数设置成友元函数或者在类里面写私有成员变量的Get方法Java常用。
friend ostream operator (ostream out, Date d);友元函数只需要配合上friend关键字在日期类里面加上一条声明即可此时在该函数体就可以使用对象中的私有成员变量。该声明不受类中访问限定符的限制。
重载 同理也应该重载成全局的。
istream operator (istream in, Date d)
{in d._year d._month d._day;return in;
}注意两个形参in和d都不能用const修饰前者是因为in本质上是一个对象在进行流插入的时候会改变对象里面的一些状态值而后者是因为我们就是希望通过流插入往d里面写入数据所以也不能加const修饰。
小TipsC中的流插入和流提取可以完美的支持自定义类型的输入输出而C语言的scanf和printf只能支持内置类型这就是C相较于C语言的一个优势。
四、const成员
将const修饰的成员函数称为const成员函数const修饰类的成员函数实际上修饰的是该成员函数隐含的*this表明该成员函数中不能修改调用该函数的对象中的任何成员。这样一来不仅普通对象可以调用该成员函数权限的缩小const对象也能调用该成员函数权限的平移。经过const修饰的成员函数它的形参this的类型就是const T* const this。
bool Date::operator(const Date d) const//用const修饰
{if (_year d._year){return true;}else if (_year d._year _month d._month){return true;}else if (_year d._year _month d._month _day d._day){return true;}else{return false;}
}对于所有的关系运算符重载函数都应该加const修饰因为它们不会改变对象本身。
总结 并不是所有的成员函数都要加const修饰要修改对象成员变量的函数是不能加const修饰的例如重载的、-等而成员函数中如果没有修改对象的成员变量可以考虑加上const修饰这样不仅普通对象可以调用该成员函数权限的缩小const对象也能调用该成员函数权限的平移。
五、取地址及const取地址操作符重载
Date* operator()
{cout Date* operator() endl;return this;
}
const Date* operator() const
{cout const Date* operator() const endl;return this;
}int main()
{Date d1(2023, 7, 22);const Date d2(2023, 7, 22);cout d1 endl;cout -------- endl;cout d2 endl;return 0;
}这俩取地址运算符重载函数又构成函数重载因为它们的默认形参this指针的类型不同一个用const修饰了另一个没有。const对象会去调用const修饰的取地址运算符重载函数。
小Tips这两个重载属于类的默认成员函数我们不写编译器会自动生成所以这两个运算符重载一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容。 结语 今天的分享到这里就结束啦如果觉得文章还不错的话可以三连支持一下您的支持就是春人前进的动力