网站下载链接怎么做,高清免费素材网,人才培训网,沈阳小程序建设【C】—— 类与对象#xff08;四#xff09; 6、赋值运算符重载6.1、运算符重载6.1.1、基础知识6.1.2、调用方法6.1.3、前置 与 后置 的重载6.1.4、注意事项6.1.5、 和 运算符重载6.1.5.1、 和 基础6.1.5.2、日期类 operator 的实… 【C】—— 类与对象四 6、赋值运算符重载6.1、运算符重载6.1.1、基础知识6.1.2、调用方法6.1.3、前置 与 后置 的重载6.1.4、注意事项6.1.5、 和 运算符重载6.1.5.1、 和 基础6.1.5.2、日期类 operator 的实现6.1.5.2.1、类型不匹配问题6.1.5.2.2、连续赋值问题 6.1.5.3、日期类 operator 的实现 6.1.6、总结 6.2、赋值运算符重载5.2.1、赋值运算符重载的特点6.2.1、赋值运算符重载进阶6.2.2、赋值重载拷贝与拷贝构造 7、日期类的完整实现7.1、Date.h7.2、Date.cpp 6、赋值运算符重载
6.1、运算符重载
6.1.1、基础知识 像、— 、% 、 等运算符只对内置有效但对于自定义类型又该怎么办呢 就拿 D a t e Date Date类 来说日期与日期如何比较大小呢简单地只使用一个 / 肯定是无法完成任务的。 解决上述问题就需要用到 运算符重载
运算符重载的基本特点 当运算符被用于类类型对象时C 允许我们通过运算符重载的形式制定新的含义。C 规定类类型对象使用运算符时必须转换成调用对应运算符重载若没有对应的运算符重载则会编译报错 运算符重载是具有特殊名字的函数它的名字是由 o p e r a t o r operator operator 和后面要定义的运算符共同构成。和其他函数一样它也有返回类型和参数列表以及函数体 重载运算符函数的参数个数和该运算符作用的运算对象一样多。一元运算符有一个参数二元运算符有两个参数二元运算符的左侧运算对象向传给第一个参数右测运算对象传给第二个参数 如果一个重载运算符函数是成员函数则它的第一个运算对象默认传给隐式的 t h i s this this指针因此运算符重在作为成员函数是参数比运算对象少一个。 我们先写一个运算符重载函数来判断两个日期是否相等
class Date
{
public:Date(int year 2024, int month 1, int day 1){_year year;_month month;_day day;}private:int _year;int _month;int _day;
};bool operator(const Date d1, const Date d2)
{return d1._year d2._year \d1._month d2._month \d1._day d2._day;
}但现在还有一个问题operator 在类外成员变量是私有没有访问权限 有三种方法 把成员变量的访问权限改为公有提供 G e t Y e a r GetYear GetYear 等函数友元函数之后讲 这次三种方法我们都不用(虚晃一枪)直接把它变成成员函数不香吗
class Date
{
public:Date(int year 2024, int month 1, int day 1){_year year;_month month;_day day;}bool operator(const Date d1){return _year d1._year \_month d1._month \_day d1._day;}private:int _year;int _month;int _day;
};当运算符重载函数是成员函数时它的第一个参数默认是 t h i s this this指针因此参数比运算对象少一个。
6.1.2、调用方法 那运算符重载函数怎么调用呢
它有两种调用方法
int main()
{Date d1(2023, 1, 1);Date d2(2024, 1, 1);//法一如普通函数一样的调用方法d1.operator(d2);//法二写成运算符的调用方法推荐d1 d2;return 0;
}6.1.3、前置 与 后置 的重载 大家有没有想过运算符怎么重载呢毕竟 前置 和 后置 的运算符重载函数名都是 operator并且参数都是一样的(都为 t h i s this this指针)怎么进行区分呢 C 规定后置 重载时增加一个 i n t int int 参数跟 前置 构成函数重载方便区分。 i n t int int 参数后面加不加形参名都可以实际编译器实际上并不接受只是为了标识 我们简单写一下 D a t e Date Date 的 前置 和 后置
//假设operator已经实现//后置
Date operator(int)
{Date tmp *this;*this 1;return tmp;
}//前置
Date operator()
{*this 1;return *this;
}可以看见 后置 比 前置 多了两次拷贝构造因此对自定义类型尽量使用前置以减少拷贝提高效率
6.1.4、注意事项 运算符重载后其优先级和结合性与对应的内置类型运算符保持一致。 重载后运算符的优先级和结合性该怎样还是怎样比如 ∗ * ∗ 的优先级依然比 ± 高。 不能通过链接语法中没有的符号来产生新的操作符比如 operator .* :: sizeof ?: .以上5个运算符不能重载选择题例常考大家要记住 运算符重载可以构成 函数重载。 比如对于日期类以下两个运算符重载就构成函数重载
//以下两个运算符重载虽然函数名相同但参数不同构成运算符重载
//日期 - 天数
int operator-(const Date d, int day);
//日期 - 日期
Date operator-(const Date d1, const Date d2);运算符重载和函数重载虽然都有 “重载” 二字但他们之间并没有关系。 函数重载指的是函数名相同参数不同。运算符重载指的是重新定义这个运算符的行为。再就是 两个运算符重载的函数可以构成函数重载 5. 重载操作符至少有一个类类型参数不能通过运算符重载改变内置类型对象的含义如int operator(int x, int y) 当然不是全部参数都要自定义类型有一个即可比如上面的int operator-(const Date d, int day);
一个类需要重载那些运算符是看哪些运算符重载有意义 比如 D a t e Date Date类 重载 o p e r a t o r operator operator- 就有意义重载成 o p e r a t o r operator operator 就没有意义日期 日期是什么意思呢
6.1.5、 和 运算符重载
6.1.5.1、 和 基础 我们想重载 和 来输入输出日期可不可以呢我们来试一下 首先我们要知道cout 是 ostream 类型的对象cin 是 istream 类型的对象 为什么对内置类型 能 自动识别类型并输出 呢 是因为函数重载 类中把所有的内置类型都重载好了他能自动识别出函数重载调用相应的函数进行输出。
6.1.5.2、日期类 operator 的实现 那对于自定义类型我们想继续使用 就需要我们自己重载运算符
我们来写日期类的流插入
class Date
{
public :Date(int year 1, 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;
};上述代码的 o u t out out 是 c o u t cout cout 的引用 o u t out out 就是 c o u t cout cout 我们测试一下
int main()
{Date d(2024, 1, 1);d.operator(cout);return 0;
}运行结果 可以看到没有问题。
6.1.5.2.1、类型不匹配问题 运算符重载还有另外一种调用方式我们也试一试
int main()
{Date d(2024, 1, 1);cout d;return 0;
}报错了 为什么呢两种调用方法不是等价的吗 别急我们仔细看一下报错信息“没有找到接受“Date”类型的右操作数的运算符或没有可接受的转换” 简单来说就是 参数不匹配 对于运算符重载函数来说当操作数为二元操作数左侧的运算对象默认传给第一个参数右侧的传给的第二个参数。而成员函数的第一个参数默认是 t h i s this this指针上述的传参方式会导致类型不匹配 因此正确的传参是
int main()
{Date d(2024, 1, 1);d cout;return 0;
}但d cout;这样传参是不是感觉怪怪的想要cout d;传参那只能把 t h i s this this指针 放在第二位ostream out放在第一位。 那有没有办法把this指针放在第二个参数位置上呢没有。因为 t h i s this this指针 是隐式的我们无法改变C规定 t h i s this this指针 默认是第一个参数 因此operator 的重载只能放在类外成 全局函数 。 但全局函数有一个问题不能访问私有 我们可以在类中加一个友元声明下期介绍这样类外的函数就可以访问私有啦
class Date
{//有元声明friend void operator(ostream out, const Date d); public :Date(int year 1, int month 1, int day 1):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;
};void operator(ostream out, const Date d)
{out d._year 年 d._month 月 d._day 号 endl;
}6.1.5.2.2、连续赋值问题 但现在还有一个问题它不支持连续输出
int main()
{Date d1(2024, 1, 1);Date d2(2023, 1, 1);//无法连续输出cout d1 d2;return 0;
}怎么解决呢 首先我们要知道运算符的结合性是从左往右的因此先进行运算的是cout d1。 而cout d1应该要有一个返回值以便能支持接下来 d 2 d2 d2 的输出那这个返回值是什么呢很显然是 c o u t cout cout 因此正确的重载应该是这样
ostream operator(ostream out, const Date d)
{out d._year 年 d._month 月 d._day 号 endl;return out;
}这里用 引用返回返回 o u t out out即 c o u t cout cout
6.1.5.3、日期类 operator 的实现 接下来我们实现流提取
istream operator(istream in, Date d)
{cout 请依次输入年月日;in d._year d._month d._day;return in;
}流提取中Date d不能加 c o n s t const const因为提取出的数据要放在对象里面是要改变对象的。 6.1.6、总结 当运算符被用于类类型对象时C 允许我们通过运算符重载的形式制定新的含义。C 规定类类型对象使用运算符时必须转换成调用对应运算符重载若没有对应的运算符重载则会编译报错 运算符重载是具有特殊名字的函数它的名字是由 o p e r a t o r operator operator 和后面要定义的运算符共同构成。和其他函数一样它也有返回类型和参数列表以及函数体 重载运算符函数的参数个数和该运算符作用的运算对象一样多。一元运算符有一个参数二元运算符有两个参数二元运算符的左侧运算对象向传给第一个参数右测运算对象传给第二个参数 如果一个重载运算符函数是成员函数则它的第一个运算对象默认传给隐式的 t h i s this this指针因此运算符重在作为成员函数是参数比运算对象少一个。 运算符重载后其优先级和结合性与对应的内置类型运算符保持一致 不能通过链接语法中没有的符号来创建新的操作符比如 operator .* :: sizeof ?: . 以上5个运算符不能重载选择题例常考大家要记住 重载操作符 至少有一个类类型参数不能通过运算符重载改变内置类型对象的含义 一个类需要重载运算符是看哪些运算符重载后有意义比如 D a t e Date Date类 重载 o p e r a t o r operator operator- 就有意义但是重载 o p e r a t o r operator operator 就没有意义 重载 运算符时有 前置 和 后置运算符重载函数名都是 o p e r a t o r operator operator无法很好的区分。C 规定后置 重载时增加一个 i n t int int 形参跟 前置 构成函数重载方面区分 重载 和 时需要重载为全局函数因为重载为成员函数 t h i s this this指针 默认抢占了第一个形参位置第一个形参位置是左侧运算对象调用时就变成了 对象 cout不符合使用习惯和可读性。重载为全局函数把 o s t r e a m ostream ostream / i s t r e a m istream istream 放到第一个形参位置就可以了第二个形参位置当类类型对象。 6.2、赋值运算符重载 赋值运算符重载是一个默认成员函数用于完成两个已经存在的对象直接的拷贝赋值这里要注意跟拷贝构造区分拷贝构造用于一个对象拷贝初始化给另一个要创建的对象
5.2.1、赋值运算符重载的特点
赋值运算符重载的特点 赋值运算符重载是一个运算符重载规定必须重载为成员函数。赋值运算符重载的参数建议写成 c o n s t const const 当前类类型引用否则会传值传参会有拷贝 有返回值且建议写成当前类类型的引用引用返回可以提高效率有返回值目的是为了支持连续赋值场景 我们先尝试写一个赋值运算符重载 void operator(const Date d){_year d._yaer;_month d._month;_day d.day;}注赋值运算符重载必须为成员函数 拷贝构造的参数建议写成引用。但不像调用拷贝构造赋值重载可以写成传值传参但传值传参要多调用一次拷贝构造赋值重载是有返回值的返回值建议是自身类类型的引用。为什么呢主要是存在连续复制的情况 int main()
{Date d1(2024, 1, 1);Date d2;Date d3;d3 d2 d1;return 0;
}赋值这个运算符是支持连续赋值的重载后的赋值函数也应支持连续赋值 比如
int i, j, k;
i j k 1;这个表达式是怎么执行的呢 “” 运算符的结合性是从右往左的1 赋值给 k k k k k k 1 这个表达式是有返回值的返回左操作数 k k k k k k 赋值给 j j j返回 j j j j j j 再赋值给 i i i返回 i i i。 所以上述赋值重载正确写法是 Date operator(const Date d){_year d._yaer;_month d._month;_day d.day;return *this;}这里返回值建议用引用传值返回的话会多调用 一次拷贝构造
6.2.1、赋值运算符重载进阶
赋值运算符重载的进阶特点 没有显式实现时编译器会自动生成一个默认赋值运算符重载默认运算符重载行为跟默认拷贝构造函数类似对内置类型成员变量会完成值拷贝/浅拷贝一个字节一个字节的拷贝对自定义类型成员变量会调用他的拷贝构造 像 D a t e Date Date 这样的类成员变量全是内置类型且没有指向什么资源编译器自动生成的赋值运算符重载就可以完成需要的拷贝所以不需要我们显式实现赋值运算符重载。像 S t a c k Stack Stack 这样的类虽然也都是内置类型但是 /_ a a a 指向了资源编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求所以需要我们自己实现深拷贝对指向的资源也进行拷贝。像 M y Q u e u e MyQueue MyQueue 这样的内部主要是自定义类型 S t a c k Stack Stack 成员编译器自动生成的赋值运算符重载会调用 S t a c k Stack Stack 的赋值运算符重载也不需要我们显式实现 M y Q u e u e MyQueue MyQueue 的赋值运算重载。这里有一个小技巧如一个类显式实现了析构并释放了资源那它就需要显示写赋值运算符重载否则不需要 这两点特点与拷贝构造是类似的这里就不再赘述。
6.2.2、赋值重载拷贝与拷贝构造 这里赋值重载拷贝与拷贝构造很容易混淆我们来看看 需要知道赋值运算符重载与默认构造函数都是默认成员函数 赋值运算符重载用于完成两个已经存在的对象直接的拷贝赋值而 拷贝构造是用于一个对象拷贝初始化给另一个要创建的对象 7、日期类的完整实现 下面是日期类的完整实现以便大家检验一下类与对象的成果
7.1、Date.h
#pragma once#includeiostream
using namespace std;
#includeassert.hclass Date
{//友元声明friend ostream operator(ostream out, const Date d);friend istream operator(istream in, Date d);public://检查日期是否合法bool CheckDate() const;//构造函数Date(int year 2024, int month 1, int day 1);//打印日期void Print() const;// 频繁调用Ĭinline函数int GetMonthDay(int year, int month) const{assert(month 0 month 13);static int monthDayArray[13] { -1, 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 monthDayArray[month];}bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator(const Date d) const;bool operator!(const Date d) const;Date operator(int day) const;Date operator(int day);Date operator-(int day) const;Date operator-(int day);// d1;// d1.operator(0);Date operator(int);// d1;// d1.operator();Date operator();// d1--;// d1.operator--(0);Date operator--(int);// --d1;// d1.operator--();Date operator--();// d1 - d2int operator-(const Date d) const;private:int _year;int _month;int _day;
};ostream operator(ostream out, const Date d);
istream operator(istream in, Date d);7.2、Date.cpp
#includeDate.h//检查日期是否合法
bool Date::CheckDate() const
{if (_month 1 || _month 12|| _day 1 || _day GetMonthDay(_year, _month)){return false;}else{return true;}
}//构造函数
Date::Date(int year, int month, int day)
{_year year;_month month;_day day;if (!CheckDate()){cout 非法日期:;Print();}
}// void Date::Print(const Date* const this)
void Date::Print() const
{cout _year / _month / _day endl;
}// d1 d2
bool Date::operator(const Date d) const
{if (_year d._year){return true;}else if (_year d._year){if (_month d._month){return true;}else if (_month d._month){return _day d._day;}}return false;
}// d1 d2
bool Date::operator(const Date d) const
{return *this d || *this d;
}bool Date::operator(const Date d) const
{return !(*this d);
}bool Date::operator(const Date d) const
{return !(*this d);
}bool Date::operator(const Date d) const
{return _year d._year _month d._month _day d._day;
}bool Date::operator!(const Date d) const
{return !(*this d);
}// d1 100
Date Date::operator(int day)
{if (day 0){return *this - (-day);}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}// d1 100
Date Date::operator(int day) const
{Date tmp *this;tmp day;return tmp;
}// d1 - 100
Date Date::operator-(int day) const
{Date tmp *this;tmp - day;return tmp;
}// d1 - 100
Date Date::operator-(int day)
{if (day 0){return *this (-day);}_day - day;while (_day 0){--_month;if (_month 0){_month 12;--_year;}_day GetMonthDay(_year, _month);}return *this;
}// d1;
// d1.operator(0);
Date Date::operator(int)
{Date tmp *this;*this 1;return tmp;
}// d1;
// d1.operator();
Date Date::operator()
{*this 1;return *this;
}// d1 - d2
int Date::operator-(const Date d) const
{int flag 1;Date max *this;Date min d;if (*this d){max d;min *this;flag -1;}int n 0;while (min ! max){min;n;}return n * flag;
}ostream operator(ostream out, const Date d)
{out d._year 年 d._month 月 d._day 日 endl;return out;
}istream operator(istream in, Date d)
{while (1){cout 请依次输入年月日:;in d._year d._month d._day;if (!d.CheckDate()){cout 输入日期非法:;d.Print();cout 请重新输入!!! endl;}else{break;}}return in;
}注 int GetMonthDay(int year, int month) const是用来获取该年该月日期因为需要多次调用因此直接在类内实现成内联函数 o p e e r a t o r opeerator opeerator 可通过 o p e e r a t o r opeerator opeerator 复用实现。 o p e e r a t o r opeerator opeerator 可通过 o p e e r a t o r opeerator opeerator 复用实现但更推荐前者 o p e r a t o r operator operator 达到实现效率一致 o p e e r a t o r opeerator opeerator 后者要多调用两次拷贝构造和一次赋值对于 ! 他们 6 个 任意实现 2 个即可其他 4 个均可 通过逻辑间相互复用实现 好啦本期关于类和对象的知识就介绍到这里啦希望本期博客能对你有所帮助。同时如果有错误的地方请多多指正让我们在C语言的学习路上一起进步