网站建设技术标准,网站建设国家标准,中国建筑工程网施工资料,深圳高端网站设计建设CSDN的uu们#xff0c;大家好。这里是C入门的第十一讲。 座右铭#xff1a;前路坎坷#xff0c;披荆斩棘#xff0c;扶摇直上。 博客主页#xff1a; 姬如祎 收录专栏#xff1a;C专题 目录
1. 知识引入
2. 运算符重载
2.1 operator()
2.2 operator()
2.3 o… · CSDN的uu们大家好。这里是C入门的第十一讲。 · 座右铭前路坎坷披荆斩棘扶摇直上。 · 博客主页 姬如祎 · 收录专栏C专题 目录
1. 知识引入
2. 运算符重载
2.1 operator()
2.2 operator()
2.3 operator
2.4 operator
2.5 operator
2.6 operator
3. 运算符重载总结 1. 知识引入
来看下面的代码我们定义了一个日期类实现了他的构造函数和拷贝构造函数。现在我们想要比较两个日期的大小如果是你的话你会怎么写呢
class Date
{
public://构造函数Date(int year 0, int month 0, int day 0){_year year;_month month;_day day;}//拷贝构造函数Date(const Date d){_year d._year;_month d._month;_day d._day;}private:int _year;int _month;int _day;
};int main()
{return 0;
} 你可能会写一个成员函数假设你写的是比较两个对象谁比较小。你可能会写出一个名为Less的函数里面封装了两个Date类对象比较大小的逻辑。 bool Less(const Date d){if (_year d._year|| (_year d._year _month d._month)|| (_year d._year _month d._month _day d._day)){return true;}return false;}
然后你实例化出来两个对象运行代码发现并没有问题非常nice。 但是这样做是不是有点麻烦呢于是你想要是可以直接这样写该多好呀
cout (d1 d2) endl;
直接这样写肯定是不行的。对于内置类型编译器知道如何去比较但是对于自定义类型编译器就无从下手了因为他不知道你定义的类型里面有哪些成员变量比较逻辑是什么
那我们该怎么做呢C祖师爷本贾尼设计出了一个叫做运算符重载的东东能够满足你的一切幻想。
2. 运算符重载 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字operator后面接需要重载的运算符符号。 函数原型返回值类型 operator操作符(参数列表) 注意 1不能通过连接其他符号来创建新的操作符比如operator。 2重载操作符必须有一个自定义类型参数用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义。 3作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this。 4.* :: sizeof ?: . 以上5个运算符不能重载。这个经常在笔试选择题中出现。 上面提到过重载运算符的函数可以写在类里面也可以写在类外面 (赋值运算符重载是例外哦前一讲提到过赋值运算符是类的6个默认成员函数之一你如果在类外重载赋值运算符那么编译器就会提供默认的赋值运算符重载的函数从而与你类外重载的赋值运算符冲突。)
2.1 operator()
好的我们现在就来重载一个小于运算符试试吧下面的代码是在类内书写的版本
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;}return false;
}
我们回看运算符重载的特性一个运算符有几个操作数那么operator该运算符的形参列表就会有几个参数。我们在类里面实现的符号的重载只有一个参数那是因为类成员函数都有一个隐藏的this。 在我们重载了运算符之后d1 d2的调用逻辑还是d1.operator(d2)。通过上面的汇编代码我们也能够看出来
那么在类外重载运算符应该怎么书写呢大家不妨一试谨记operator函数参数列表参数的个数和操作数的个数是一样的哦
bool operator(const Date d1, const Date d2)
{if (d1._year d2._year){return true;}else if (d1._year d2._year d1._month d2._month){return true;}else if (d1._year d2._year d1._month d2._month d1._day d2._day){return true;}return false;
} 如果你是这么写的那么恭喜你写对了。但是如果在定义Date类的时候你将成员变量全部设置为私有你这里就会出现私有成员无法访问的报错提示。这该怎么解决呢 方法一直接将成员变量的访问权限修改为public。(成员变量暴露不推荐) 方法二提供能够获取到成员变量的函数比如GetYear() 等等。(太麻烦不推荐) 方法三友元。在C中有一个关键字friend。他能够将一个函数或者一个类设置为另一个类的友元。设置为友元之后这个函数或者类就可以直接访问另一个类被private修饰的成员变量和成员函数。 语法friend 函数或者类的声明。 例如我们的operator的全局函数想要访问Date类中的私有成员就可以在Date类中将operator声明为Date类的友元 方法四直接把operator函数写在类的里面(这里的写在里面可以是operator函数的定义在类里面实现在类外面)。(推荐的做法) 2.2 operator()
什么是赋值运算符重载显然就是重载这个运算符哇
赋值运算符重载有什么用? 已经存在的两个对象当我们将一个对象通过 运算符赋值给另一个对象时编译器会自动调用赋值运算符重载。
C的前一讲我们知道赋值运算符重载也是类的6个默认成员函数之一。我们没有书写编译器会自动提供的那么编译器自动提供的赋值运算符重载会干什么呢想必经过之前的学习你已经能猜个大概了吧。编译器提供的赋值运算符重载对内置类型直接赋值对自定义类型会调用该自定义类型的赋值运算符重载。我们来尝试写一个赋值运算符重载的函数吧
void operator(const Date d)
{_year d._year;_month d._month;_day d._day;
} 我们可以看到重载运算符之后对象的赋值就算是成功了通过汇编代码我们能够看到对象的赋值实际上就是调用了赋值运算符的重载函数 但是 运算符的重载还没完我们在写代码的时候肯定见过这样的代码吧
int a, b, c;
int d 1;
a b c d;
没错就是连续赋值的问题我们来看看我们写的operator能否做到连续赋值呢 因此对于我们的 operator 还需要修改一下我们的返回值来确保连续赋值的正确性。 Date operator(const Date d){_year d._year;_month d._month;_day d._day;return *this;} 因为我们的Date类里面的成员变量全部都是内置类型因此编译器默认提供的赋值运算符重载和拷贝构造就够用了我们就不需要动手自己写了但是如果说你定义的类里面有成员变量维护了堆区的空间那么就需要你自己动手写拷贝构造和赋值运算符重载了赋值运算符重载为什么也要写呢原因就是因为编译器默认提供的赋值运算符重载对于内置类型是直接赋值嘛任何的指针都是内置类型当我们的指针维护有堆区的空间时直接复制就会有两个指针指向同一块堆区的空间在对象销毁的时候就会发生二次析构的问题(前提是你正确书写了析构函数没正确书写析构函数的话就是内存泄漏了)。
下面我们来看看赋值运算符重载与拷贝构造的区别 拷贝构造用一个已经存在的对象来初始化一个正在实例化的对象是构造函数 赋值运算符重载已经存在两个对象将一个对象成员变量的值赋值给另一个对象的成员变量。 直接看代码请问下面的代码 Date d2 d1; 调用的是赋值运算符重载还是拷贝构造呢
int main()
{Date d1(2004, 01, 01);Date d2 d1;
}
答案是拷贝构造函数啦当你分不清的时候就看看拷贝构造函数与赋值运算符重载的定义这里是用 d1 这个对象去初始化一个正在实例化的对象当然是拷贝构造啦 在赋值运算重载的实现里面我们习惯加上一个判断判断是否是自己给自己赋值如果是的话就不用赋值直接结束函数即可因为没有太大的意义嘛
//不会修改d的内容建议加上const
Date operator(const Date d)
{if (this d)return *this;_year d._year;_month d._month;_day d._day;return *this;
}
2.3 operator
我们重载这个运算符的目的就是为了能够让日期对象加上一个常数然后计算加上该常数之后的日期时多少该函数的原型Date operator(int day);
我们之前看到运算符重载的函数要求时必须要有一个自定义类型这里看上去虽然没有但是还是有一个隐藏的this呢
的逻辑应该怎么写呢因为在增加天数的过程中会涉及月份或者年份的增加我们需要能够判断是否到达了增加月份的条件因此我们可以封转一个函数用于返回这个月的天数例如GetMonthDay(int year, int month)这个函数用于返回当前月的天数。在operator的函数体中我们直接让对象的_day加上传过来的形参然后循环与当前月的实际天数作比较如果大于当前月的实际天数我们就让月份 1同时让_day减去当前月的实际天数。直到_day小于当前月的实际天数为止在此过程中注意到月份如果大于12则需要将年份1同时将月份修正为1。
//获取一个月的实际天数 不可以返回int 因为 29 没法寻址
int GetMonthDay(int year, int month)
{static int daysArr[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 daysArr[month];}
}//重载的 运算符
Date operator(int day)
{_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}
2.4 operator 在实现日期类的时候运算符的重载也是很有必要的运算符右前置和后置两种。我们先来讲讲前置前置是先然后返回之后的对象
实现方式很简单啊因为我们之前就已经实现过 operator 了我们只需要复用这个接口就行了
//返回引用的目的是提高效率
Date operator()
{*this 1;return *this;
}
我们来看后置应该怎么写因为前置和后置的运算符相同操作数也相同祖师爷本贾尼就规定后置的运算符重载需要多加一个形参以示区分。函数的原型 Date operator(int)没错是不需要写形参的这个int只用来与前置构成函数重载的接受形参并没有多大的意义。因此我们可以不用写形参后置返回的是之前的那个对象因此我们还需要创建一个对象来保存之前的值用于函数的返回值
//返回值不能是引用不可返回局部对象的引用
Date operator(int)
{Date tmp *this;*this 1;return tmp;
}
我们可以看到在调用前置的时候只传了一个实参调用后置的时候传了两个实参 2.5 operator 这是什么操作符在C的第二讲提到过这个叫做流插入运算符cout hello world ;你肯定见过嘛我们要打印Date对象总不能也去写一个Print函数撒太麻烦了
我要直接cout d1;
于是我们就需要重载流插入运算符了
我们观察下面的图发现cout是ostream的对象嘿嘿传参的问题就得到了解决 我们先来在类里面重载流插入运算符看看是否正确吧
ostream operator(ostream out)
{out _year - _month - _day endl;return out;
}
这样写没什么大问题但是调用的时候就非常奇怪因为我们的this指针位于形参的第一个想要调用operator就必须让Date对象在前面Date对象在前面确实能够调用了但是非常不符合我们cout的使用习惯因此我们需要将流插入的重载写在全局 我们把流插入运算符的重载写在全局并且让Date对象位于第二个参数并且让返回值是一个ostream的对象就能实现正确习惯的连续流插入了
注意形参不能加const流插入是要往对象里面写东西的你加上const就没法写东西了
ostream operator(ostream out, const Date d)
{out d._year - d._month - d._day;return out;
}还有一个知识点假设你写了一个Display()函数来打印Date对象中的成员变量 这里仅用这个例子来讲解知识点重载流插入更方便嘛 你发现普通对象调用Display()没有任何问题但是const 对象调用Display()就会出问题这是为什么呢 我们知道Display()的参数中有一个隐藏的this指针指向调用该函数的对象。当我们用普通对象调用Display()传过去的this指针是这样的Date*当我们用const对象调用 Display() 传过去的this指针是这样的const Date*。而我们的Display()的形参是这样的Date* 。显然是不能用Date* 去接受const Date* 的实参的会发生权限的放大 因此我们只要修改Display()的形参让他的this指针是 const Date* 就能解决这个问题了祖师爷想来想去最后决定在成员函数后面加上const此const 修饰 *this 代表this指向的内容不允许修改 2.6 operator 流插入你会写了流提取问题应该也不大cin是一个istream的对象好的快去尝试写写吧
istream operator(istream in, Date d)
{in d._year d._month d._day;
}
对象d不可以加const修饰因为你要向里面写入内容嘛记得加友元或者封转函数返回私有的成员变量哦
3. 运算符重载总结 以上实现的运算符重载并不包含所有但是包含了大部分的运算符重载的知识。 例如operator()operator-()operator() 等等。 重载运算符的时候能复用就尽量复用哈比如你重载了 和 运算符那么 , , , ! 都可以复用你重载的 和 运算符 运算符重载并不是每一个都需要写根据你的需求重载相对应的运算符即可