呼和浩特 网站建设,广东如何进行网站制作排名,邯郸网站设计价格,深圳建设网站公司文章目录 前言一.运算符重载二.const成员三.取地址重载总结前言
上一期我们讲到类的6个默认构造函数中的拷贝构造函数#xff0c;这一期我们继续往下讲#xff0c;当然难点肯定是运算符重载了。 一、运算符重载
运算符重载是c为了增强代码的可读性引入了运算符重载#xf… 文章目录 前言一.运算符重载二.const成员三.取地址重载总结前言
上一期我们讲到类的6个默认构造函数中的拷贝构造函数这一期我们继续往下讲当然难点肯定是运算符重载了。 一、运算符重载
运算符重载是c为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数相似。
函数名字为关键字operator后面接需要重载的运算符符号
函数原型返回值类型operator操作符参数列表
注意
1.不能通过连接其他符号来创建新的操作符。比如operator
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符其含义不能改变。例内置的整形 不能改变的含义比如把加法弄成乘法之类的。
4.作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this。
5.有五个运算符是不能重载的。1 .*点星 2 ::(域名限定符) 3 sizeof 4 ?:(三目操作符) 5 . (成员访问操作符)
class Date
{
public:Date(int year 10, int month 10, int day 10){_year year;_month month;_day day;}void print(){cout _year 年 _month 月 _day 日 endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023,2,10);Date d2(2023,2,5);d2.print();return 0;
} 像上面的d1和d2两个参数该如何比较大小呢以前在C语言我们通常写一个函数是传两个日期的地址过去然后挨个用指针访问去比较最后返回布尔值这样会非常的麻烦而c的运算符重载正好解决了这个问题。
class Date
{
public:Date(int year 10, int month 10, int day 10){_year year;_month month;_day day;}void print(){cout _year 年 _month 月 _day 日 endl;}bool operator(const Date d){return _year d._year _month d._month _day d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023,2,10);Date d2(2023,2,10);cout (d1 d2) endl;return 0;
} 通过上面的代码和图片大家应该可以看到运算符重载对于自定义类型有多方便而运算符重载的规则我们也讲过那么写到类外什么样子呢
由于类外不可访问类内私有成员可以先将私有成员改为共用或者知道友元函数的用友元 类外的区别就是多一个参数因为在类内有this指针。 cout打印d1d2加括号的原因是流插入操作符的优先级高于。
符号的重载
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 operator(const Date d){return !(*this d);} 我们可以发现运算符很多都是实现一两个其他的就可以复用了。小于等于不就是大于的取反吗只需要知道类内函数有隐藏的参数this默认指向第一个操作数即可。
符号:
bool operator(const Date d){return !(*this d)!(*thisd);} 小于就是大于等于的取反。
符号:
bool operator(const Date d){return !(*this d);} 大于等于就是小于的取反。
符号
bool operator!(const Date d){return !(*this d);} 赋值运算符重载
赋值运算符的重载格式
1.参数类型 const T,传递引用可以提高传参效率。
2.返回值类型T 返回引用可以提高返回的效率有返回值的目的是为了实现连续赋值
3.检测自己给自己赋值因为两个一样的变量再去赋值会消耗空间
4.返回*this 要复合连续赋值的含义
注意赋值运算符的重载并不是强制要求参数为这里要和拷贝构造区分赋值运算符即使用传值调用也可以使用不会发生无穷调用因为在传自定义类型参数的时候先拷贝构造一个临时变量然后将这个变量赋值给变量即可。
Date operator(const Date d){_year d._year;_month d._month;_day d._day;return *this;} 一般来说赋值给另一个对象是不需要返回值的但是为了实现连续赋值那么就必须返回被赋值的那个变量由于被赋值的对象不会被销毁所以为了不调用拷贝构造函数浪费空间直接使用引用返回即可。如果有人写成d1 d1这样的代码那么不就白白的浪费了空间吗所以我们直接判断一下
Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}
this是左操作数的地址d是右操作数的地址这样的好处就是即使赋值时出现操作数一样的情况也不会白白浪费空间。
注意运算符的顺序不是从右往左也不是从左往右这是要看操作符的结合性的。
比如 是从右往左开始的 是从左往右开始的
我们之前说过类的6个默认函数即使我们不写编译器也会写一个默认的那么默认的能完成赋值重载的任务吗这个问题和拷贝赋值是一样的对于内置类型编译器可以完成赋值但是对于像栈那样需要开不同空间的必须我们手动去写一个赋值重载。 前面讲拷贝构造的时候忘记了一个细节那就是向上图中红色框起来的也是拷贝构造有些人会有疑问这里不是用赋值重载了吗其实并不是赋值重载的调用是针对两个已经实例化好的或定义的对象而像上图中d5还没有定义出来是在实例化的过程中是用d1初始化d5用一个对象初始化一个对象用的是拷贝构造。
下面我们利用运算符重载实现一个计算日期的小程序小程序的功能包括日期天数日期天数日期-天数日期-天数日期的前置后置日期的前置--后置--日期-日期相差多少天下面先展示源代码然后我们一个函数一个函数讲解
class Date
{
public:Date(int year 2023, int month 2, int day 10){if (year 0 (month 1 month 12) (day 0 day GetMonthDay(year, month))){_year year;_month month;_day day;}else{cout 日期不合法 endl;exit(-1);}}void Print(){cout _year 年 _month 月 _day 日 endl;}bool operator(const Date d){return _year d._year _month d._month _day d._day;}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 operator(const Date d){return !(*this d);}bool operator(const Date d){return !(*this d) !(*this d);}bool operator(const Date d){return !(*this d);}bool operator!(const Date d){return !(*this d);}Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}int GetMonthDay(int year, int month){int MonthArray[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;}else{return MonthArray[month];}}//日期 天数Date operator(int day){if (day 0){*this - -day;return *this;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;}//日期 天数Date operator(int day){Date tmp(*this);tmp day;return tmp;}//前置Date operator(){*this 1;return *this;}//后置Date operator(int){Date tmp(*this);*this 1;return tmp;}//日期 - 天数Date operator-(int day){if (day 0){*this -day;return *this;}_day - day;while (_day 0){_month--;if (_month 0){_year--;_month 12;}_day GetMonthDay(_year, _month);}return *this;}//日期 - 天数Date operator-(int day){Date tmp(*this);tmp - day;return tmp;}//前置--Date operator--(){*this - 1;return *this;}//后置--//int参数 仅仅是为了占位根前置重载区分Date operator--(int){Date tmp(*this);*this - 1;return tmp;}//日期相减(得到的是天数)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){n;Min;}return n * flag;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 12, 3);Date d2(2023, 6, 4);//cout (d1 d2) endl;/*d1 --d2;d1.Print();d2.Print();*///d2.Print();//cout (d1 - d2) endl;d2 d1 - -100;d2.Print();return 0;
} 首先我们在构造函数中初始化的时候要确保日期是合法的不能出现月数小于0或者大于12的并且天数要大于0小于当月最大天数所以我们在构造函数中加了一个判断当日期不合法时就输出日期不合法并且退出程序。
int GetMonthDay(int year, int month){int MonthArray[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;}else{return MonthArray[month];}}
因为我们计算日期的时候必须知道每个月是多少天而且还有闰年二月是29天的情况所以我们写了一个函数得到每个月的天数数组有13个是因为数组是从0开始我们为了方便直接在第一个位置加一个0即可。
接下来我们讲解日期天数
Date operator(int day){if (day 0){*this - -day;return *this;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;} 在这里由于不确定要加多少天如果加1000天那么就要重复上图的步骤所以这是一个循环当天数大于当月最大天数的时候就进入循环需要注意的是当月数加到13就说明越界了要及时改为合法月数。因为就是会改变本身的值并且在函数结束后日期也没有被销毁所以我们采用传引用的方式减少拷贝构造的消耗。判断天数是否小于0是因为我们不知道有人会不会写成-数如果是负数那就是-一个正数。
Date operator(int day){Date tmp(*this);tmp day;return tmp;} 日期天数那么是不会改变本身的所以我们需要拷贝构造一个变量这时候直接复用操作符即可由于tmp是函数中的临时变量函数结束就会销毁所以不能采用传引用的方式。
//前置Date operator(){*this 1;return *this;} 前置是先在使用所以直接1返回即可。
//后置Date operator(int){Date tmp(*this);*this 1;return tmp;} 后置是先使用再也就是说我们必须用一个变量接收开始的日期然后自己1返回开始没有1的那个值即可由于这个值是临时变量所以只能用传值返回。需要注意的是编译器区分前置和后置的点是后置的参数有一个int这个int是占位符没有实际作用也不用写参数写一个int即可。
//日期 - 天数Date operator-(int day){if (day 0){*this -day;return *this;}_day - day;while (_day 0){_month--;if (_month 0){_year--;_month 12;}_day GetMonthDay(_year, _month);}return *this;} 需要注意的是减去天数后如果大于0就说明本月的天数够用不需要向上个月借等于0也需要借因为没有2月0日为了让借到的天数是上个月的所以月份--后再加上借的天数与同理都要判断day是否为负数为负数就变成了上一个正数那为什么和-我们没有判断呢因为和-我们是用和-复用的。
//日期 - 天数Date operator-(int day){Date tmp(*this);tmp - day;return tmp;} 这里与一样复用就可以。前置--和后置--也与一样。
//日期相减(得到的是天数)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){n;Min;}return n * flag;}
日期相减实现起来也很简单我们以之前的为负以后的为正先定义两个变量Max和Min来存放两个日期我们默认是第一个日期大于第二个日期当第一个大于第二个日期的时候就说明是之前的那么让flag为负如果第一个日期小于第二个日期就让flag为正。然后我们用n来记录天数当两个日期不相等就进入循环让n和小的那个日期自加直到相等我们就能计算出有多少天了。
那么我们每次调用函数去打印日期是不是不方便呢能不能直接用cout打印日期呢答案是可以的我们通过重载运算符即可完成。我们现在类中声明然后再类外实现。 那么我们写了一个为什么不能调用呢 我们只能通过调用函数的方式去调用和我们想的并不一样这怎么办呢我们用cout不能直接调用的原因是操作符的左边是左操作数右边是右操作数而我们常用的打印习惯是右操作数那么我们先来看一下左操作数是否能正确调用 我们发现是可以正确调用的但是很奇怪我们喜欢写到右边。解决这个问题之前我们要先知道运算符重载在类中第一个操作符是*this而像我们那样的写法很明显out是右操作数了我们要的是out去作为左操作数想要让out成为左操作数将运算符重载写到外面了不就解决了吗因为外面是没有*this的。 但是当我们写到定义在类外发现不能访问类内的成员了这里的解决方式有多个我们讲两个简单的即可第一个将类内私有改为公有如下图 第二个是用友元函数我们将这个函数设为类的友元就可以访问类的所有成员了。 为什么编译器的cout支持多个打印我们的不可以呢这是因为我们没有返回值我们应该要将out返回这样就能连续打印了因为out出了作用域没有被销毁所以我们可以返回其引用。 这样就解决了打印自定义类型的问题接下来我们再重载一下cin与cout一样只需要改一下参数即可。 为什么输入的参数d我们不加const了呢这是因为我们输入会改变const的值如果加了const就不能改变了。
在这里需要注意一下类里面的短小函数适合做内联的函数直接是在类里面定义的。
const成员
将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。
class A
{
public:void Print(){cout _a endl;}
private:int _a;
};
int main()
{/*A aa;aa.Print();*/const A aa; //权限的放大aa.Print();return 0;
} 在这里为什么会报错呢因为aa的类型是const A* ,而传递给this指针后变成了A* 所以这里是权限的放大想要解决只需要给this指针也用const修饰如下图 这里函数后面的const修饰的是this指针让this指针变成了const A*。
而上图是权限的缩小权限的缩小是没有问题的调用Func函数从A*变成了const A*.
总结内部不改变成员变量的成员函数最好加上constconst对象和普通对象都可以调用比如下面的代码
#include assert.h
class Array
{
public:int operator[](int i){assert(i 10);return _a[i];}const int operator[](int i) const{assert(i 10);return _a[i];}private:int _a[10];int _size;
};
void Func(const Array d)
{for (int i 0; i 10; i){cout d[i] ;}
}
int main()
{Array ay;for (int i 0; i 10; i){ay[i] i * 10;}for (int i 0; i 10; i){cout ay[i] ;}Func(ay);return 0;
}
取地址重载
取地址重载和赋值运算符重载一样都会由编译器自己生成当然有需求也可以自己去写。 本来自定义类型用运算符必须自己重载但是赋值运算符和取地址重载编译器生成的就够用。 当然如果我们不想让别人获取我们的地址我可可以返回一个假地址如上图所示 以上就是取地址重载的用法总之不是非常必要是不用去自己写取地址重载和const取地址重载的。 总结
学会运算符重载是学习c必备的技能c独特的就是自定义类型而运算符重载可以解决自定义类型使用运算符的问题。