网站开发 进度表,东莞定制网页设计,产品开发思路,郑州住房和城乡建设局网站#x1f388;个人主页#xff1a;库库的里昂 ✨收录专栏#xff1a;C从练气到飞升 #x1f389;鸟欲高飞先振翅#xff0c;人求上进先读书。 目录
⛳️推荐
一、运算符重载的引用
二、运算符重载
三、赋值运算符重载
1 .赋值运算符重载格式:
2 .赋值运算符只能重载成… 个人主页库库的里昂 ✨收录专栏C从练气到飞升 鸟欲高飞先振翅人求上进先读书。 目录
⛳️推荐
一、运算符重载的引用
二、运算符重载
三、赋值运算符重载
1 .赋值运算符重载格式:
2 .赋值运算符只能重载成类的成员函数不能重载成全局函数:
3 .用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝:
四、日期类的实现
1. 重载关系运算符
重载运算符:
重载运算符:
重载运算符:
重载运算符:
重载运算符:
重载!运算符:
2. 完善日期类
获取每个月份的天数:
重载运算符:
重载运算符:
为什么不用复用: 重载-运算符:
重载-运算符:
重载日期-日期:
重载前置和后置运算符:
重载前置--和后置--运算符:
和运算符:
重载运算符:
重载运算符:
五、const成员
const权限问题:
总结:
六、取地址及const取地址操作符重载 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站通俗易懂风趣幽默忍不住分享一下给大家。点击跳转到网站 一、运算符重载的引用
本章将以日期类为例进行展开叙述 通常比较两个操作数的大小会写成下述方式 int main()
{int i 1, j 2;i j;return 0;
}但是对于日期类采用上述方式会发生报错 class Date
{
public:Date(int year 2023, int month 9, int day 25){_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, 10, 1);Date d2(2022, 2, 15);d1.Print();d2.Print();d1 d2;return 0;
}因为日期类是我们自己定义的属于一种自定义类型它的大小比较方式编译器是不知道的而像int等内置类型这是原生语言定义的知道规则来比较可以直接用、去比较两个内置类型变量的大小但自定义类型不可以哦
最常见的解决方式:
创建一个函数来通过一 一对比年月日来实现日期类的比较不过会出现关于私有的问题这个函数是写在类外面的意味着日期类的成员变量是private私有的在类外面就无法访问所以在这个函数里面是访问不到类对象中的_year、_month、_day所以x1._year等都是错误的会发生报错要想实现该函数的功能可以采用下面3种方法
class Date
{
public:Date(int year 2023, int month 9, int day 25){_year year;_month month;_day day;}void Print(){cout _year / _month / _day endl;}
//private:int _year;int _month;int _day;
};
bool DateLess(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._monthx1._dayx2._day){return true;}else{return false;}
}
int main()
{Date d1(2023, 10, 1);Date d2(2022, 2, 15);d1.Print();d2.Print();DateLess(d1, d2);return 0;但是对于这种函数写法相比于直观的、来看显然不是很直观对于不清楚的other来说读代码是很困难的所以就引入了运算符重载
二、运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。
函数名字为关键字operator后面接需要重载的运算符符号。函数原型返回值类型 operator操作符(参数列表)
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._monthx1._dayx2._day){return true;}else{return false;}
}
int main()
{Date d1(2023, 10, 1);Date d2(2022, 2, 15);//coutd1d2endl;至于为什么不写成coutd1d2endl是因为的优先级高于d1d2与operator(d1d2)本质上都是调用运算符重载所以两者写法是等价的只不是一个显示调用一个没有显示调用cout (d1 d2) endl;cout (operator(d1, d2)) endl;return 0;
}上述代码就是对运算符的一个重载此时两个日期类对象就可以直接用来比较大小d1 d2本质上就是调用运算符重载函数此外还需要解决一个问题上面的运算符重载函数是写在类外面的日期类的成员变量是private私有的该运算符重载函数还是不能用。
注意
不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个自定义类型参数 int x1, int x2都是内置类型是不可以的规定必须有一个自定义类型的参数
也就是说只能对自定义类型进行重载内置类型不可以
bool operator(int x1, int 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;}else{return false;}
}用于内置类型的运算符其含义不能改变例如内置的整型不能改变其含义作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this
d1d2两个操作数是不可以随意换位置的左操作数就是第一个参数(this)右操作数就是第二个参数(d)
d1.operator(d2)bool operator(Date* this, const Date d2)
这里需要注意的是左操作数是this指向调用函数的对象和上面一个意思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)
{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;}
}
int main()
{Date d1(2023, 10, 1);Date d2(2022, 2, 15);cout (d1 d2) endl;cout (d1.operator(d2)) endl;成员函数---符合调用规则因为有一个是隐含的参数d1.operator(d2)return 0;
}
——————————————————————————————————————————————————————————————————————————————————
类外面定义函数
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;}else{return false;}
}
int main()
{Date d1(2023, 10, 1);Date d2(2022, 2, 15);cout (d1 d2) endl;cout (operator(d1, d2)) endl;return 0;
}不能改变操作符的操作数个数一个操作符是几个操作数那么重载的时候就有几个参数.* 和 *不一样 * 是可以重载的 、 域作用限定符 ::、 sizeof 、三目运算符?: 、对象变量取成员 . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现选择题。
三、赋值运算符重载 首先要区分赋值运算符和拷贝构造
赋值两个已经存在的对象进行拷贝拷贝构造一个已经存在的对象去初始化另一个对象
Date d1(2023, 10, 1);
Date d2(2023, 10, 7);
d1 d2; ---调用赋值运算符重载
Date d3 d1; ---调用拷贝构造函数或者写成这种也是拷贝构造Date d3(d1);1 .赋值运算符重载格式:
参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义
Date operator(const Data d)
{if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;
}2 .赋值运算符只能重载成类的成员函数不能重载成全局函数:
我们可以重载赋值运算符。不论形参的类型是什么赋值运算符都必须定义为成员函数。
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}int _year;int _month;int _day;
};赋值运算符重载成全局函数注意重载成全局函数时没有this指针了需要给两个参数
Date operator(Date left, const Date right)
{if (left ! right){left._year right._year;left._month right._month;left._day right._day;}return left;
}原因赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数。
3 .用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝:
用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
四、日期类的实现
下面日期类的实现是通过定义和声明分离来写的所以需要加Date:: 1. 重载关系运算符 对于关系运算符有以下几种、 、、、、!因为它们之间存在的逻辑关系可以通过复用来实现就比如想要知道一个数a是否另一个数b就可以通过判断a是否b来实现所以只需要写一个小于和等于的逻辑之后的复用即可 重载运算符:
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;}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);
}2. 完善日期类
对于日期类的计算若想知道100天以前或是100天以后是哪一天是非常有价值的但是一个日期类型和一个整型是可以相加相减吗当然是可以的可以通过重载运算符来实现。
获取每个月份的天数:
int Date::GetMonthDay(int year, int month)
{static : GetMonthDay肯定会重复调用每次调用还要定义一遍这个数组会有一定的消耗const static 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;}return monthArray[month]; 返回的是数组中这个值的临时拷贝
}注意对于2月是分闰年和平年的但是其他月份是固定不变的所以可以通过一个数组来存放每个月的天数并且以下标作为每个月的月份所以不是monthArray[12]而是monthArray[13]。其中要把数组定义为静态数组目的是为了防止每次调用还要定义一遍这个数组会有一定的消耗。 此外对于判断闰年和平年这里还要注意先把month2写在最前面因为只有2月是需要判断的 重载运算符: 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;
}注意对于if语句为什么复用了-运算符是因为加一个负的天数是算多少天以前的日期所以当天数为负的时候可以复用-。 重载运算符: 运算符和运算符本质是一样的所以不需要再写一遍只需要复用运算符就可以了但是运算后等价于aaba也是会改变的而运算符运算后a是不会改变的 Date Date::operator(int day)
{Date tmp(*this);复用tmp day; return tmp;
}注意要计算nm的最终结果n和m这两个数值计算后是不会改变的所以一个日期加天数原日期是不会改变的而原日期也就是this指针指向的内容所以也就是不能修改this指针指向的内容对此要先利用拷贝构造函数创建一个和*this一模一样的对象tmp在tmp的基础上去加天数。此外tmp是一个临时变量出了作用域会销毁所以不能引用返回 为什么不用复用: 注意上面的复用只能存在一个不能同时都去复用同时存在会出现错误。
重载-运算符:
对于上述是复用还是复用好给出原因正因如此-和-也是一样的原理所以先写-再用-复用-
Date Date::operator-(int day)
{这里和上述是一个意思if (day 0){return *this (-day);}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this;
}重载-运算符:
Date Date::operator-(int day)
{Date tmp(*this);tmp - day;return tmp;
}重载日期-日期:
int Date::operator-(const Date d)
{Date max *this;Date min d;int flag 1;if (max min){max d;min *this;flag -1;}int n 0;while (min ! max){min;n;}return n * flag;
}计算的结果是两个日期之间的天数所以返回值是int要想知道两个日期之间相隔的天数可以设置一个计数器n让小日期一直加到大日期就可以知道两个日期之间相隔的天数。
重载前置和后置运算符:
d3 前置返回之后的 d3 — d3.operator()
——————————————————————————————————————————— d3 后置返回之前的 d3 — d3.operator(0) 这里加参数是为了区分前置和后置 加一个int参数进行占位跟前置构成函数重载进行区分本质后置调用编译器进行特殊处理
自定义类型用前置比较好后置需要拷贝代价比较大
Date Date:: operator()
{*this 1; 复用了return *this;
}
——————————————————————————————————————————————————————————————————————————————————
Date Date:: operator(int)
{Date tmp(*this);拷贝构造*this 1;return tmp;
}int main()
{Date d3(2023, 7, 27);下面两者等价不过是前者显式调用对于后置或者后置--显式调用需要传个参数不过并无卵用Date ret1 d3.operator(0);//Date ret1d3--; ret1.Print();d3.Print();Date ret2 d3;ret2.Print();d3.Print();return 0;
}重载前置--和后置--运算符:
Date Date:: operator--()
{*this - 1; 复用了-return *this;
}
————————————————————————————————————————————————————————————————————————————————
Date Date:: operator--(int)
{Date tmp(*this);*this - 1;return tmp;
}int main()
{Date d3(2023, 7, 27);下面两者等价不过是前者显式调用对于后置或者后置--显式调用需要传个参数不过并无卵用Date ret1 d3.operator--(0);//Date ret1d3--; ret1.Print();d3.Print();Date ret2 --d3;ret2.Print();d3.Print();return 0;
}和运算符:
对于内置类型可以直接使用、编译器可以根据数据的类型进行打印本质上是因为库中进行运算符重载。但是对于自定义类型编译器是不知道怎样打印的所以要想使用打印自定义类型日期类是要对运算符进行重载 我们在使用C进行输入输出的时候会用到cin和cout它们俩本质上都是对象cin是istream类实例化的对象cout是ostream类实例化的对象。
而、不用像C语言的printf和scanf那样char对应%cint对应%dfloat对应%f是因为运算符重载本质上是函数对这些不同的内置类型分别进行了封装在运算符重载的基础上又实现了函数重载所以、支持自动识别类型。
重载运算符:
Date.h
void operator(ostream cout);//放在类里面的Date.cpp
void Date::operator(ostream cout)//---operator(Date* this,ostream cout)
{cout _year - _month - _day endl;
}Test.cppcout d1;
d1 cout;//虽然可以运行但是不符合使用习惯和价值
当在类中重载运算符会发现用coutd1打印是会发生报错的这是因为对于一个双目运算符的重载它的左操作数cout会传递给运算符重载函数的第一个形参(this)右操作数(d1)会传递给运算符重载函数的第二个形参(cout)对于类的成员函数它的第一个形参是默认的this指针该指针是日期类类型的指针和cout的类型不匹—operator(Date* this,ostream cout)。为了解决上述问题可以写成d1cout此时就相当于d1.operator(cout)会把d1的地址传给this指针形参再用一个ostream类型的对象来接收cout虽然可以运行但是不符合使用习惯和价值。 最好的解决方式把重载成全局函数 把重载成全局函数就不会有默认的this指针同时还可以根据需要设置形参的顺序void operator(ostream out,const Date d)出了作用域cout还在所以可以用引用返回。
Date.h
void operator(ostream cout,const Date d);//全局的Date.cpp
void operator(ostream cout, const Date d)
{cout d._year - d._month - d._day endl;
}Test.cpp
cout d1;把重载成全局函数是解决了参数问题但是默认在该全局函数中是访问不到日期类中的私有成员变量为了解决这个问题可以把该运算符重载函数设置成友元函数 友元声明在日期类里面第一行加上一条下述声明此时在该全局函数体就可以使用日期类中的私有成员变量。 friend void operator(ostream cout, const Date d);
此声明不受类中访问限定符的限制 连续的
Date.h
ostream operator(ostream cout,const Date d);Date.cpp
ostream operator(ostream cout, const Date d)
{cout d._year - d._month - d._day endl;return cout;
}
Test.cpp
cout d1 d2 endl;对于上述coutd1是一个函数调用返回类型是void但是对于连续的coutd1d2endlcout先是作为左操作数打印d1,左向右执行coutd1返回void后面的d2就会报错所以要写成ostream operator(ostream cout, const Date d)返回一个ostream类型的cout接着cout作为左操作数再次函数调用打印d2 当然这里改了友元声明也要改
friend ostream operator(ostream cout, const Date d);重载运算符: 希望通过流插入往d里面写入数据所以不能加const修饰。istream operator(istream cin, Date d); Date.h
istream operator(istream cin, Date d);Date.cpp
istream operator(istream cin, Date d)
{cin d._year d._month d._day;return cin;
}Test.cpp
cin d3;友元声明
friend istream operator(istream cin, Date d);C中的流插入和流提取可以完美的支持自定义类型的输入输出
五、const成员
将const修饰的“成员函数”称之为const成员函数const修饰类成员函数 void Print() const 实际修饰该成员函数隐含的this指针 void Print(const Date* this) 表明在该成员函数中不能对类的任何成员进行修改。这样不仅普通对象可以调用该成员函数权限的缩小const对象也能调用该成员函数权限的平移。也防止了权限的放大。
const权限问题:
#includeiostream
using namespace std;
class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout Print() endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}void Print() const // --- void Print(const Date* this){cout Print()const endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}
int main()
{Test();return 0;
}权限不可以放大 总结:
对于关系运算符可以考虑加上const修饰因为并不会改变对象本身但是并不是所有的成员函数都要加const修饰要修改对象成员变量的函数例如重载的、-等是不能加const修饰的因为会修改成员本身而成员函数中如果没有修改对象的成员变量可以考虑加上const修饰这样不仅普通对象可以调用该成员函数权限的缩小const对象也能调用该成员函数权限的平移
六、取地址及const取地址操作符重载
#includeiostream
#includeDate.h
using namespace std;
Date* Date::operator()
{cout Date* operator() endl;return this;
}
const Date* Date::operator()const
{cout const Date* operator()const endl;return this;return nullptr; 不想被取到有效地址可以这样写
}
int main()
{Date d1(2023,10,1);const Date d2(2023,10,1);cout d1 endl;cout endl;cout d2 endl;return 0;
}总结这两个取地址运算符重载函数又构成函数重载。这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容