前言:
前面篇章学习了C++对于C语言的语法优化,接下来继续学习,C++的类和对象中Date日期类实现、const成员函数和运算符重载等知识。
/知识点汇总/
声明和定义分离
Date.cpp Date.h Date_main.cpp
Date日期类实现目的是学习,运算符<,<=,>,>=,==,!=,重载及逻辑复用
#pragma once
#include
#include
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (!checkInvalid())
{
cout << "构造函数日期非法" << endl;
//exit(-1);
}
}
//赋值重载
Date& operator=(const Date& d);
//添加检查函数,检查操作的日期是否合法
bool checkInvalid();
//运算符<, <= , >, >= , == , != ,重载及逻辑复用
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);
//处理年、月、日应用运算符重载实现
//GetMonthDay频繁调用,可用内敛,然后如果把函数写成类成员函数本质也就是inline函数
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
//int monthdays[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 };
//放在静态区,细节节省空间资源的重复调用开辟。因为此函数本身会被高频调用,所以处理好细节就更优化
static int monthdays[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 };
//闰年
//if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
//细节决定成败,把month写在&&之前,有利于执行效率
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthdays[month];
}
//d1 += 100
Date& operator+=(int day);
//d1 + 100
Date operator+(int day);
//d1 - 100
Date operator-(int day);
//d1 -= 100
Date& operator-=(int day);
//++d1
Date operator++();
//d1++
//这里特殊处理,强制添加int型参数与编译器自洽,构造函数重载,以区分前置++,后置++
Date operator++(int);
//--d1
Date operator--();
//d1--
//这里特殊处理,同理,强制添加int型参数与编译器自洽
Date operator--(int);
//d1 - d2
//日期-日期=天数
int operator-(const Date& d);
//流插入"<<"运算符重载,和流提取">>"运算符重载
//属于C++库中ostream的对象
//这里就写成成员函数,测试流的方向。
/*void operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}*/
//友元声明:可间接的突破私有的访问限定符私。
friend ostream& operator<<(ostream& out, const Date& d);
//友元声明
friend istream& operator>>(istream& in, Date& d);
//void Print();
//const修饰成员函数权限平替,实质为this const Date*
void Print() const;
private:
int _year;
int _month;
int _day;
};
//分析可知,不能写成成员函数。则写成全局函数,声明和定义分离,或者写成引用inline内联在.h展开都可以
//当然,作为全局参数就可以加入类名(调用的类),也可以让ostream作为第一个参数
//void operator<<(ostream& out, const Date& d);
//解决类似于连续赋值的,连续流插入,则需改返回值,作为下一个流插入的对象
ostream& operator<<(ostream& out, const Date& d);
//同理:流提取
istream& operator>>(istream& in, Date& d);
#include "Date.h"
//在类外调用需要适用于操作符
//巧用复用处理大于小于等于的关系
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
}
//void Date::Print()
void Date::Print() const
{
cout << _year << " - " << _month << " - " << _day << endl;
}
bool Date::checkInvalid()
{
if (_year <= 0
|| _month > 12 || _month < 1
|| _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
//d1 < d2
bool Date::operator<(const Date& d)
{
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)
{
return *this < d || *this == d;
}
//d1 > d2
bool Date::operator>(const Date& d)
{
return !(*this <= d);//逻辑取反
}
//d1 >= d2
bool Date::operator>=(const Date& d)
{
return !(*this < d);
}
//d1 == d2
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//d1 != d2
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
//写法一:
//d1 += 100
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
++_year;
}
}
return *this;
}
//d1 + 100
//Date Date::operator+(int day)更简便的思路和写法,就是像<,<=,>,>=,==,!=,重载一样,引用逻辑复用
//复用+=,实现+
Date Date::operator+(int day)
{
//拷贝构造
Date tmp = *this;
//+=复用
tmp += day;
return tmp;
}
//写法二:
//d1 + 100
//Date Date::operator+(int day)
//{
// //拷贝构造,创建临时的类实现加运算,就不会改变原时期的值
// Date tmp(*this);
// //等价写法:
// //Date tmp = *this;//拷贝构造是一个已存在的对象拷贝给创建的新对象
//
// tmp._day += day;
// while (tmp._day > GetMonthDay(tmp._year, _month))
// {
// tmp._day -= GetMonthDay(tmp._year, tmp._month);
// ++tmp._month;
// if (tmp._month == 13)
// {
// tmp._month = 1;
// ++tmp._year;
// }
// }
// //局部对象,函数结束后,生命周期就结束,所以不能用引用返回
// return tmp;
//}
//那么如果想要复用+,实现+=?
//d1 += 100
//Date& Date::operator+=(int day)
//{
// //赋值重载 + 复用+
// *this = *this + day;
// return *this;
//}
//上面两种写法,哪个更好呢?
//答:写法一更好;因为比较直观的对比开辟的额外空间。
//即:两个方法的+都开辟同样的资源,但而方法二的+=还需要复用+,所以比第一个方法再额外开辟一次空间资源等消耗,所以方法一更优。
//d1 -= 100
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
//先处理借位关系,再获取day
_day += GetMonthDay(_year, _month);
}
return *this;
}
//d1 - 100
Date Date::operator-(int day)
{
//拷贝构造函数
Date tmp = *this;
tmp -= day;
return tmp;
}
//++d1
Date Date::operator++()
{
*this += 1;
return *this;
}
//d1++
//这里特殊处理,强制添加int型参数与编译器自洽,构造函数重载,以区分前置++,后置++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
//--d1
Date Date::operator--()
{
*this -= 1;
return *this;
}
//d1--
//这里特殊处理,同理,强制添加int型参数与编译器自洽
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
//d1 - d2
//日期-日期=天数
int Date::operator-(const Date& d)
{
int flag = 1;//正
//假设法,假定大小日期
Date max = *this;
Date min = d;
//假设错误纠正,始终保证max放大的日期,min放小的日期
if (max < min)
{
flag = -1;//负
max = d;
min = *this;
}
int n = 0;
//这里复用!=,比复用<=更利用效率
while (min != max)//小日期一直++,直到与大日期相等,即可得天数
{
//复用++
++min;
++n;
}
return n * flag;
}
//void Date::operator<<(ostream& out, const Date& d)
//不是成员函数,不用域操作符了
//void operator<<(ostream& out, const Date& d)
//继续解决类似于连续赋值的,连续流插入,则需改返回值,作为下一个流插入的对象
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//继续解决,类的成员变量私有访问限定符的问题
//方法1:
//1.像之前一样写一个回调函数,返回成员变量
//Getyear()、Getmonyh()、Getday()
//2.友元声明 --> friend关键字
//friend ostream& operator<<(ostream& out, const Date& d)
//同理:流提取
istream& operator>>(istream& in, Date& d)
{
cout << "请输入年/月/日:>" ;
in >> d._year >> d._month >> d._day;
if (!d.checkInvalid())
{
cout << "输入的日期非法" << endl;
exit(-1);
}
else
return in;
}
#include "Date.h"
int main()
{
Date d1;
Date d2;
//运算符<,<=,>,>=,==,!=,重载及逻辑复用
cout << (d1 < d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 > d2) << endl;
cout << (d1 >= d2) << endl;
cout << (d1 == d2) << endl;
cout << (d1 != d2) << endl;
return 0;
}
处理年、月、日应用运算符重载实现计算
#include "Date.h"
int main()
{
Date d1(2024, 1, 29);
Date d2 = d1 + 60;
Date d3 = d1 += 20;
d1.Print();
d2.Print();
d3.Print();
Date d5 = d2 - 60;
d5.Print();
d2 -= 60;
d2.Print();
d5++;
d5.Print();
d5--;
d5.Print();
++d5;
d5.Print();
--d5;
d5.Print();
Date d6(2024, 1, 29);
Date d7(2024, 8, 1);
cout << d7 - d6 << endl;
return 0;
}
流插入"<<“运算符重载,和流提取”>>"运算符重载
cout:C++库中ostream的对象
cin:C++库中istream的对象
#include "Date.h"
int main()
{
Date d1(2024, 1, 29);
Date d2 = d1 + 20;
d1.Print();
d2.Print();
//cout << d2 - d1 << endl;
//流插入"<<"运算符重载
//属于C++库中ostream的对象
//cout << d2;//此时编译重载失败,报错:二元“ << ”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
//分析原因:
//作为成员函数重载,this指针占据第一个参数,则Date参数就必须是左操作数了
//void operator<<(ostream& out)
//d1.operator<<(cout);
//d1 << cout;//发现翻过来写,被流插入到了d1类里了,与实际想要的不服合。
//我们要的是,cout输出d1才对。现在反而是cout改变了d1,正确的应该d1流插入cout,然后cout输出。
//所以必须以ostream作为第一个参数,使得this不能作为第一个参数。但是作为成员函数规则规定,this必须作为成员函数的第一个参数。
//所以:
//1.提出static修饰,使其归入静态区。
//2.写成全局函数
cout << d2;//此时重载成功
//d1 = d2 = d3类似于连续赋值的操作,所以返回值类型应该是类
cout << d2 - d1 << endl;
cout << d2 - d1 << d2 - d1 << endl;
//同理:流提取">>"运算符重载
//C++库中istream的对象
//cin >> d2;//此时编译重载失败,报错:二进制“ >> ”: 没有找到接受“std::ostream”类型的左操作数的运算符(或没有可接受的转换)
//解决
//必须以istream作为第一个参数,使得this不能作为第一个参数。但是作为成员函数规则规定,this必须作为成员函数的第一个参数。
//所以:
//1.提出static修饰,使其归入静态区。
//2.写成全局函数
cin >> d2;//此时重载成功
cout << d2;
//其它情况正常使用,原本的规则。
int i = 0;
cout << "请输入一个整数:";
cin >> i;
cout << i << endl;
//增加日期类的合理性,添加检查函数,检查操作的日期是否合法
//CheckInvalid()
Date d3(2024, 2, 31);
d3.Print();
return 0;
}
将const 修饰的成员函数称为const成员函数,const修饰类的成员函数,实际修饰的该成员函数中的隐含参数this指针,表明在该成员函数中不能对类的任何成员进行修改。
#include "Date.h"
int main()
{
//const 修饰成员函数
const Date d1(2024, 1, 31);
//d1.Print();//此时Print()无法调用
//因为被const修饰的类,再去调用类中的成员函数,属于权限的放大;而权限只能兼容缩小,不能使其放大造成了非法访问/调用
//解决方法,理解为权限平替,使其权限范围平行。
//即:void Print() const;
d1.Print();//此时Print()正常访问调用
//另外,虽然此时Print()被const修饰,但是当大权限的类访问时,是可以兼容的。
//即,权限的缩小
Date d2(2024, 2, 1);
d2.Print();
const int i = 0;
//因为i这里拷贝,j的改变不影响,所以不存在权限的范围变化
int j = i;
//权限放大 -- 通常是指针或引用才存在权限放大
//r的改变要影响i
//int& r = i;
//权限放大 -- 通常是指针或引用才存在权限放大
const int* p1 = &i;
//int* p2 = p1;
return 0;
}
小结:是否给成员函数加const修饰,取决于成员函数本身,是否需要被修改。
简单理解为,类似于文件的,只读或读写。
1.成员函数如果是一个 对成员函数只进行读访问的 函数–>建议加上const,这样const对象和非const对象都能使用;
2.成员函数如果是一个 对成员函数进行读/写访问的 函数–>不能加const,否则const对象和非const对象都不能访问成员变量。
- const对象可以调用非const成员函数吗?不能,权限放大
- 非const对象可以调用const成员函数吗?能,权限缩小
- const成员函数内可以调用其它的非const成员函数吗?不能,权限放大
- 非const成员函数内可以调用其它的const成员函数吗?能,权限缩小
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
#include
using namespace std;
class A
{
public:
//日常不需要自己写构造函数,编译器默认生成的已经够用了。
//除非使&,不需要拿地址。(或一些整蛊真假地址)
A* operator&()
{
return this;
}
const A* operator&() const
{
return this;
}
};
int main()
{
A aa1;
const A aa2;
//日常不需要自己写,编译器默认生成的已经够用了。
//除非使&,不需要拿地址。
cout << &aa1 << endl;
cout << &aa2 << endl;
return 0;
}
小结:
取地址&及const+&这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!