手机网站分页,网站开发感想,秦皇岛市地图,微信开发工具的公司#x1f31f;个人主页#xff1a;落叶 #x1f31f;当前专栏: C专栏 目录 赋值运算符重载
运算符重载
赋值运算符重载
日期类实现
运算符重载和运算符重载
运算符重载进行复用
运算符重载
运算符重载
运算符重载
运算符重载!
获取某年某月的天数… 个人主页落叶 当前专栏: C专栏 目录 赋值运算符重载
运算符重载
赋值运算符重载
日期类实现
运算符重载和运算符重载
运算符重载进行复用
运算符重载
运算符重载
运算符重载
运算符重载!
获取某年某月的天数
日期或天数
日期-或-天数
前置和后置
日期-日期
日期类实现代码
Date.h
Date.cpp
test.cpp 运算符重载的流插入和流提取
流插入
流提取 取地址运算符重载
const成员函数
取地址运算符重载 赋值运算符重载
运算符重载 当运算符被⽤于类类型的对象时C语⾔允许我们通过运算符重载的形式指定新的含义。C规定类类型对象使⽤运算符时必须转换成调⽤对应运算符重载若没有对应的运算符重载则会编译报错。 运算符重载是具有特殊名字的函数他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样它也具有其返回类型和参数列表以及函数体。 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数 如*//-- ⼆元运算符有两个参数 (如 /- //) ⼆元运算符的左侧运算对象传给第⼀个参数右侧运算对象传给第⼆个参数。 如果⼀个重载运算符函数是成员函数则它的第⼀个运算对象默认传给隐式的this指针因此运算符重载作为成员函数时参数⽐运算对象少⼀个。 运算符重载以后其优先级和结合性与对应的内置类型运算符保持⼀致。 不能通过连接语法中没有的符号来创建新的操作符⽐如operator。 .* :: sizeof ?: . ? 注意以上5个运算符不能重载。(选择题⾥⾯常考⼤家要记下) 重载操作符⾄少有⼀个类类型参数不能通过运算符重载改变内置类型对象的含义如 int operator(int x, int y) ⼀个类需要重载哪些运算符是看哪些运算符重载后有意义⽐如Date类(日期类)重载operator-就有意义日期减去日期可以得到还剩下但是天。 但是重载operator*就没有意义。 重载运算符时有前置和后置运算符重载函数名都是operator⽆法很好的区分。C规定后置重载时增加⼀个int形参跟前置构成函数重载⽅便区分。 重载和时需要重载为全局函数因为重载为成员函数this指针默认抢占了第⼀个形参位置第⼀个形参位置是左侧运算对象调⽤时就变成了 对象cout不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了第⼆个形参位置当类类型对象。 内置类型int...这些支持编译器自带的运算符但是自定义类型类类型...这些就不支持运算符了。 自定义类型编译器不知道我们定义的自定义类型是什么要干什么。 所以编译器让我们自己根据自定义类型来实现运算符重载。 下面那个代码是比较2个日期类大小用运算符重载函数operator和运算符结合来进行比较。
重载操作符⾄少有⼀个类类型参数不能通过运算符重载改变内置类型对象的含义如 int operator(int x, int y) 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数 如*//-- ⼆元运算符有两个参数 (如 /- //) ⼆元运算符的左侧运算对象传给第⼀个参数右侧运算对象传给第⼆个参数。 下面这个代码私有的成员变量不能被修改所以会报错公有就不会报错,也可以重载为成员函数。
#includeiostream
using namespace std;
class Date
{
public://公有//全缺省构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}private:私有//成员变量int _year;//年int _month;//月int _day;//日
};//--------------------------------------------------------------------bool operator(const Date x1, const Date x2)
{//比较年if (x1._year x2._year){return true;}//年相等比较月else if (x1._year x2._year x1._month x2._month){return true;}//年相等月相等比较日else if (x1._year x2._year x1._month x2._month x1._day x2._day){return true;}//都等于或大于返回falsereturn false;
}int main()
{Date d1(2024, 9, 2);Date d2(2024, 7, 4);//比较结果给tabbool tab2 operator(d1, d2);//这样写编译器会自动转换成operator(d1, d2)所以这样写比较方便bool tab d1 d2;cout tab endl;return 0;
} 重载为成员函数 重载为成员函数就不能向上面这样写了。
我们可以看到下面报错了很多。运算符函数参数太多。 如果⼀个重载运算符函数是成员函数则它的第⼀个运算对象默认传给隐式的this指针因此运算符重载作为成员函数时参数⽐运算对象少⼀个。 有隐式的this指针所以我们不需要显示写d1这个参数。
#includeiostream
using namespace std;
class Date
{
public://全缺省构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//不用写第一个参数第一个参数有隐式的this指针bool operator( const Date x2){//比较年if (_year x2._year){return true;}//年相等比较月else if (_year x2._year _month x2._month){return true;}//年相等月相等比较日else if (_year x2._year _month x2._month _day x2._day){return true;}//都等于或大于返回falsereturn false;}private:int _year;//年int _month;//月int _day;//日
};int main()
{Date d1(2024, 9, 2);Date d2(2024, 7, 4);//比较结果给tabbool tab2 d1.operator(d2);//这样写编译器会转换成 d1.operator(d2) 所以这样写比较方便bool tab d1 d2;cout tab endl;return 0;
} 这个d1类里的operator函数把d2传过去这样就可以对2个类进行比较了。 也可以这样写。
//这样写编译器会转换成 d1.operator(d2) 所以这样写比较方便bool tab d1 d2;
我们可以看到它们在汇编都是一样的。 .* :: sizeof ?: . ? 注意以上5个运算符不能重载。(选择题⾥⾯常考⼤家要记下)。 class A
{
public://成员函数void func(){cout A::func() endl;}
};
typedef void(A::* PF)(); //成员函数指针类型int main()
{// C规定成员函数要加才能取到函数指针PF pf A::func;A obj; //定义ob类对象temp// 对象调⽤成员函数指针时使⽤.*运算符(obj.*pf)();return 0;
} 重载操作符⾄少有⼀个类类型参数不能通过运算符重载改变内置类型对象的含义如 int operator(int x, int y) // 编译报错“operator ”必须⾄少有⼀个类类型的形参
int operator(int x, int y)
{
return x - y;
} 重载运算符时有前置和后置运算符重载函数名都是operator⽆法很好的区分。C规定后置重载时增加⼀个int形参跟前置构成函数重载⽅便区分。 运算符重载后面会讲假设我们已经把运算符实现好了。 前置没有任何参数使用运算符重载加1后出了作用域d1还在所以使用引用返回传值返回也行就是会调用拷贝构造。 后置必须有一个int形参⽅便和前置区分后置将d1拷贝给tab使用运算符重载加1后
这tab是在作用域创建的出了作用域就销毁了不能使用引用返回了使用传值返回把tab的数值返回。
class Date
{
public://全缺省构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//运算符重载实现后面会讲现在假设已经把运算符实现好了Date operator(int day){//..........}//d//前置编译器会自动转换成 d1.operator()Date operator(){//使用运算符重载1*this 1;//返回d1引用return *this;}//d//后置编译器会自动转换成 d1.operator(0)Date operator(int){//d1拷贝给tabDate tab(*this);//使用运算符重载1*this 1;//返回tab的值return tab;}private:int _year;//年int _month;//月int _day;//日
};int main()
{Date d1(2024, 1, 1);//前置会自动转换成 d1.operator()d1;//后置编译器会自动转换成 d1.operator(0)d1;return 0;
} 赋值运算符重载
赋值运算符重载是⼀个默认成员函数⽤于完成两个已经存在的对象直接的拷⻉赋值这⾥要注意跟拷⻉构造区分拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象。 赋值运算符重载的特点 赋值运算符重载是⼀个运算符重载规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引⽤否则会传值传参会有拷⻉。有返回值且建议写成当前类类型引⽤引⽤返回可以提⾼效率有返回值⽬的是为了⽀持连续赋值场景。没有显式实现时编译器会⾃动⽣成⼀个默认赋值运算符重载默认赋值运算符重载⾏为跟默认拷⻉构造函数类似对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)对⾃定义类型成员变量会调⽤他的赋值重载函数。 像Date这样的类成员变量全是内置类型且没有指向什么资源编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类虽然也都是内置类型但是_a指向了资源编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我们的需求所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧如果⼀个类显⽰实现了析构并释放资源那么他就需要显⽰写赋值运算符重载否则就不需要。 下面我们可以看到d1是个隐含this指针把d2的年/月/日赋值给d1但是为什么要引用返回d1呢。
(d1 d2 d3)连续赋值的时候,把d3赋值给d2然后d2引用返回d2赋值给d1。
就和下面这个int类型的连续赋值一样。 如果没有返回就不支持连续赋值了。
class Date
{
public://全缺省构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//赋值运算符重载// d1 d2Date operator(const Date d){_year d._year;_month d._month;_day d._day;//返回d1return *this;}private:int _year;//年int _month;//月int _day;//日
};int main()
{Date d1(2011, 1, 1);Date d2(2022, 2, 2);Date d3(2024, 6, 19);//d1 d2;d1 d2 d3;return 0;
}
结果: 像栈这些我们就需要用深拷贝来进行赋值了不然d2会把同一块空间赋值给d1。 日期类实现
创建3个文件Date.h / Date.cpp / test.cpp 先创建一个日期类声明一下构造函数,都是在类里声明的。
#includeiostream
#includeassert.h
using namespace std;class Date
{
public://声明全缺省构造函数Date(int year 1, int month 1, int day 1);private:int _year;//年int _month;//月int _day;//日
};
我们在Date.cpp这里实现构造函数
在Date函数里定义一个类域就行了编译器在Date.h声明找不到的时候会到类域来找。 类里声明打印实现 运算符重载和运算符重载
为什么要先实现这2个呢因为实现这2个后其他4个可以进行复用。
类里声明运算符重载和 判断小于就是先判断年年相等就判断月年/月相等判断天数。
//运算符重载小于
bool Date::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;
} 判断相等年月日相等就为真。
//运算符重载等于
bool Date::operator(const Date d)
{return _year d._year _month d._month _day d._day;
}运算符重载进行复用
当我们实现了和的运算符重载其他的就可以复用了 运算符重载 thisd会进到(运算符重载)thisd会进到(运算符重载) 小于d || 等于d 都为真
//运算符重载 小于等于
bool Date::operator(const Date d)
{return *this d || *this d;
} 运算符重载 当this小于d为真然后取反就为假了。 当this大于等于d为假然后取反就为真了。 //运算符重载 大于等于
bool Date::operator(const Date d)
{return !(*this d);
} 运算符重载
这里当this小于等于d为真然后取反就为假了。
如果是this大于d为假然后取反就为假了
//运算符重载 大于
bool Date::operator(const Date d)
{return !(*this d);
} 运算符重载!
这个就是等于的取反。
//运算符重载 ! 不等于
bool Date::operator!(const Date d)
{return !(*this d);
} 获取某年某月的天数
这个获取某年某月的天数会频繁调用我们就直接在类里实现
这样就不用到类域去找了。 assert断言月必须是1到12月。 然后把每个月的天数放到数组里0下标位置随便放个数字占位置。 这样每个月的下标就对应到每个月的天数了。 每次调用函数都会创建这个数组。 数组前面加个staic出了作用域不会被销毁了从而提高了效率。 判断闰年月是2月并且是闰年返回29。
// 获取某年某月的天数
int tab(int year, int month)
{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 (month % 4 0 month % 100 ! 0) || (month % 400 0)){return 29;}//返回当前月的天数return monthDayArray[month];
} 日期或天数
日期加天数可以计算比如100天后的日期。 思路日期的天数 加 n ,然后循环减去当前月的天数每当减一次月就1。
月13了往年进1年 1然后月为来年1月了。
//日期天数
Date Date::operator(int n)
{//让天数 n_day n;//加完后的天数大于当前月份的天数while (_day tab(_year, _month)){//减去当前月份的天数_day - tab(_year, _month);//月 1_month;//月 等于 13if (_month 13){//往年进1年 1_year;//月为来年1月给月赋值1_month 1;}}//返回加完后的日期类this这个是d1不会被销毁所以用引用返回return *this;
} 日期天数把this拷贝给add直接复用让addn。
//日期日期
Date Date::operator(int n)
{//把this拷贝给addDate add *this;//让add nadd n;//返回add这个add会被销毁不能用引用返回所以要返回数值return add;
}结果
我们可以看到9月16日100天后是12月25日。 我们可以看到d1100的日期拷贝给d2d2变了为什么d1没有变呢
运算符重载把d1拷贝给addadd100,实际上d1并没有改变。 日期-或-天数
日期-或-天数和日期或天数一样我就不多说了。 Date Date::operator-(int n)
{//让天数 - n_day - n;//小于0循环加到大于0while (_day 0){//让天数 当前_day tab(_year, _month);//月-1--_month;//月减到0if (_month 0){//年-1--_year;//月就是12月_month 12;}}//返回加完后的日期类this这个是d1不会被销毁所以用引用返回return *this;
}前置和后置 我们已经实现了前置和后置就可以1了。 //d//前置编译器会自动转换成 d1.operator()Date operator(){//使用运算符重载1*this 1;//返回d1引用return *this;}//d//后置编译器会自动转换成 d1.operator(0)Date operator(int){//d1拷贝给tabDate tab(*this);//使用运算符重载1*this 1;//返回tab的值return tab;} 日期-日期 思路先假设d1大于d2falg 1。
判断d1如果小于d2把大的值给d1小的给d2交换过falg -1。
天数计数器n循环拿最小的那个d2加到和最大的d1相等,天数计数器也要跟着加。
n乘falg正数没影响falg是负数n*falg就会变成负数。
//日期-日期
int Date::operator-(const Date d)
{//假设d1大于d2Date d1 *this;Date d2 d;int falg 1;//判断d1小于d2if (d1 d2){//把大的值给d1小的给d2d1 d;d2 *this;//控制正负数falg -1;}//天数计数器int n 0;//加到d2等于d1while (d2 ! d1){d2;n;}//n乘falg正数没影响falg是负数n*falg就会变成负数return n * falg;
} 结果
当前日期减去出生日期就可以得到从出生到现在过了多少天了。 日期类实现代码
Date.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
#includeassert.h
using namespace std;class Date
{
public://全缺省构造函数Date(int year 1, int month 1, int day 1);// 获取某年某月的天数int tab(int year, int month){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 (month % 4 0 month % 100 ! 0) || (month % 400 0)){return 29;}//返回当前月的天数return monthDayArray[month];}//打印void print();//运算符重载bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator!(const Date d);//日期或天数Date operator(int n);Date operator(int n);//日期-或-天数Date operator-(int n);Date operator-(int n);//前置/后置Date operator();Date operator(int);//前置--/后置--Date operator--();Date operator--(int);//日期-日期int operator-(const Date d);//声明友元函数运算符重载流插入//流插入friend ostream operator(ostream cout, const Date d);//流提取friend istream operator(istream cin, Date d);private:int _year;//年int _month;//月int _day;//日
};
Date.cpp #includeDate.h
//构造
Date::Date(int year, int month, int day )
{_year year;_month month;_day day;
}//打印
// Date* const this
void Date::print()
{cout _year / _month / _day endl;
}//运算符重载小于
bool Date::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;
}//运算符重载等于
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);
}//运算符重载 小于等于
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);
}//Date Date::operator(const Date d)
//{
// _year d._year;
// _month d._month;
// _day d._day;
// return *this;
//}//日期日期
Date Date::operator(int n)
{//让天数 n_day n;//加完后的天数大于当前月份的天数while (_day tab(_year, _month)){//减去当前月份的天数_day - tab(_year, _month);//月 1_month;//月 等于 13if (_month 13){//往年进1年 1_year;//月为来年1月给月赋值1_month 1;}}//返回加完后的日期类this这个是d1不会被销毁所以用引用返回return *this;
}//日期日期
Date Date::operator(int n)
{//把this拷贝给addDate add *this;//让add天数 nadd n;//返回add这个add会被销毁不能用引用返回所以要返回数值return add;
}Date Date::operator-(int n)
{//让天数 - n_day - n;//小于0循环加到大于0while (_day 0){//让天数 当前_day tab(_year, _month);//月-1--_month;//月减到0if (_month 0){//年-1--_year;//月就是12月_month 12;}}//返回加完后的日期类this这个是d1不会被销毁所以用引用返回return *this;
}Date Date::operator-(int n)
{ //把this拷贝给addDate add *this;//让add天数 - nadd._day - n;//返回add这个add会被销毁不能用引用返回所以要返回数值return add;
}//前置
Date Date::operator()
{*this 1;return *this;
}
//后置
Date Date::operator(int)
{Date tab(*this);*this 1;return tab;
}
//前置--
Date Date::operator--()
{*this - 1;return *this;
}
//后置--
Date Date::operator--(int)
{Date tab(*this);*this - 1;return tab;
}
//日期-日期
int Date::operator-(const Date d)
{//假设d1大于d2Date d1 *this;Date d2 d;int falg 1;//判断d1小于d2if (d1 d2){//把大的值给d1小的给d2d1 d;d2 *this;//控制正负数falg -1;}//天数计数器int n 0;//加到d2等于d1while (d2 ! d1){d2;n;}//n乘falg正数没影响falg是负数n*falg就会变成负数return n * falg;
}//流插入
ostream operator(ostream cout, const Date d)
{cout d._year 年 d._month 月 d._day endl;return cout;
}//流提取
istream operator(istream cin, Date d)
{cin d._year d._month d._day;return cin;
}test.cpp
#includeDate.h//int main()
//{
// //Date d1(2024, 8, 10);
// //d1.print();
// //Date d2 (d1 100);
// //d1 100;
// //d1.print();
// //d1 - 100;
// //d1.print();
// //d1 - 100;
// //d1.print();
//
// Date d1(2024, 9, 21);
// Date d2(2006, 3, 5);
// cout d1 - d2 endl;
//}int main()
{Date d1;Date d2;Date d3;//流提取cin d1 d2 d3;//流插入cout d1 d2 d3 endl;}运算符重载的流插入和流提取
重载和时需要重载为全局函数因为重载为成员函数this指针默认抢占了第⼀个形参位置第⼀个形参位置是左侧运算对象调⽤时就变成了 对象cout不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了第⼆个形参位置当类类型对象。 流插入
cout是ostream类型的对象 调⽤时就变成了对象cout不符合使⽤习惯和可读性。 所以我们需要重载为全局函数把ostream/istream放到第⼀个形参位置就可以了第⼆个形参位置当类类型对象。 重载为全局函数就访问不了私有成员了有2个方法可以解决 1.把成员变量公有。 2.用友元函数。 友元函数类和对象下会讲这里我们先用。 友元函数可以在成员函数里声明也可以在成员变量声明。 这里是把d插入到流cout所以d不用改变前面加个const。
把d的成员变量插入流cout 这样的可读性就变好起来了 。 当我们连续插入流就不行了。 当d1插入流的时候流返回d2再插入流流必须要返回才能连续插入流。 引用返回ostream流因为出了作用域cout还在。 返回cout 我们可以看到连续插入了3个日期。 流提取
cin是istream类型的对象 流提取提取我们从键盘输入的数据写入日期里。 取地址运算符重载
const成员函数 将const修饰的成员函数称之为const成员函数const修饰成员函数放到成员函数参数列表的后⾯。const实际修饰该成员函数隐含的this指针指向的内容表明在该成员函数中不能对类的任何成员进⾏修改。const 修饰Date类的Print成员函数Print隐含的this指针由 Date* const this 变为 const Date* const this 隐含的this指针都会有const修饰本身 当我们const修饰了d2我们不希望d2的内容被修改。 这个const修饰的d2内容不能被修改。
传给this指针它前面没有const修饰d2传给this指针那岂不是可以修改d2的内容。
那这不就是权限放大吗。(会报错) 要给this前面加const我们就在函数后面加const就可以了。 在函数后面加const就相当于加在了 * 号前面的位置。 加了const修饰之后this指针也不能对成员变量的内容进行修改了。 d1这种没有被const修饰传给被const修饰的this指针就是一种权限的缩小了。 结果
我们可以看到都可以正常运行。 代码
#includeiostream
using namespace std;
class Date
{
public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}// void Print(const Date* const this) constvoid Print() const{cout _year - _month - _day endl;}private:int _year;int _month;int _day;
};
int main()
{// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩Date d1(2024, 7, 5);d1.Print();const Date d2(2024, 8, 5);d2.Print();return 0;
} 取地址运算符重载 取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了不需要去显⽰实现。除⾮⼀些很特殊的场景⽐如我们不想让别⼈取到当前类对象的地址就可以⾃⼰实现⼀份胡乱返回⼀个地址。 一个是取普通对象的地址一个是取const修饰的对象地址。
这2个运算符重载我们不需要自己实现编译器默认生成的就够用了。
除⾮⼀些很特殊的场景如果我们不希望返回它的地址可以显示写返回nullptr。