1.运算符重载
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(Date&d1,Date& d2)
{
return d1._year == d2._year &&
d1._month == d2._month &&
d1._day == d2._day;
}
int main()
{
Date d1(2024, 7, 14);
Date d2(2024, 7, 14);
if (operator==(d1,d2))
{
cout << "等于" << endl;
}
else
{
cout << "不等于" << endl;
}
if (d1==d2)
{
cout << "等于" << endl;
}
else
{
cout << "不等于" << endl;
}
return 0;
}
这里我们定义bool operator==(Date&d1,Date& d2)这个函数来判断两个类是否相等,显示使用这个函数与d1d2这样使用都是一样的,operator(d1,d2)就等于d1d2
还有operator还可以和其它运算符结合,比如+,++,等等,但不能和不存在的运算符结合,比如@,还有这个运算符的操作数有几个,则对应的operator的参数就有几个,比如的操作数就是两个,则对应函数就有两个参数
以前这些参数只能对内置类型的使用,但是有了operator的话,就可以对自定义类型使用了
而且==的左操作数就对应第一个参数,右操作数就对应第二个参数,操作数和参数先后是对应的
.* :: sizeof ?: . 还有就是这五个操作数不能重载,不能与operator结合
这里我们实现的operator是全局的,但是这样会有问题,就是不能使用私有的内置类型数据,其中一个方法就是我们定义几个成员函数来获取成员变量。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int GetYear()
{
return _year;
}
//private:
int _year;
int _month;
int _day;
};
比如这个GetYear函数
还有一个方法就是定义为成员函数
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(Date& d)
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 7, 14);
Date d2(2024, 7, 14);
if (d1.operator==(d2))
{
cout << "等于" << endl;
}
else
{
cout << "不等于" << endl;
}
if (d1==d2)
{
cout << "等于" << endl;
}
else
{
cout << "不等于" << endl;
}
return 0;
}
这里我们先补充一点,就是成员函数的声明与定义没有分离的话,是默认为内联函数的,即inline,当然如果分离的话,肯定不是内联函数了
这里this指针是第一个参数,第一个操作数,左操作数。
2.赋值运算符重载
这里我们单独讲一个运算符的重载也就是赋值运算符重载,也就是=对自定义的使用,也就是d1=d2,这个与拷贝构造不同,拷贝构造,是一个对象初始化好了,另一个对象创建的时候还没初始化的时候用那个初始化好了的对象赋值
赋值运算符重载是两个已经初始化好了的东西进行的赋值
在这个之前,我们要先弄一些基本知识
int add(int a, int b)
{
return a + b;
}
int main()
{
int c = add(1, 3);
}
这个函数的返回值是int,其实是创建了一个临时变量的,比如创建了临时变量x,用来存储a+b,int x=a+b,然后再将x的值赋值给c,还有就是临时变量具有常性,也就是这是个const类型的,是const int类型的
Date func()
{
Date d(2023, 1, 1);
return d;
}
int main()
{
Date a = func();
a.print();
return 0;
}
这个的具体操作就是,先创建一个对象d,然后返回的d,返回值又是Date,所以会转化成这样,Date x=d;然后在Date a=x;也就是会拷贝构造两次
Date& func()
{
Date d(2,2,3);
return d;
}
int main()
{
Date& a = func();
a.print();
return 0;
}
这个就相当于Date&x=d;返回的是d的别名,但是d出了作用域就销毁了,你在访问这个空间数据,肯定是随机值啊
所以如果返回临时变量的值,返回类型就要用Date,如果出了这个函数这个变量不会销毁的话,生命周期仍然存在的话,为防止多次拷贝构造,可以返回别名。
这个就是返回别名的成功样例
int main()
{
int a = 0;
int b = 3;
int c = 8;
a = b = c;
return 0;
}
按照内置类型的赋值运算符规则,运算顺序是从右往左,而且为了多次能赋值,=的返回值是左边的那个数据
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
cout << "~Date()" << endl;
}
Date& operator=(Date& b)
{
_year = b._year;
_month = b._month;
_day = b._day;
return (*this);
}
void print()
{
cout << _year << "年" << _month << "月" << _day << "号" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a(1, 1, 1);
Date b(2,2,2);
Date c(3,3,3);
a = b = c;
a.print();
c.print();
b.print();
return 0;
}
为了减少拷贝构造,我们形参与返回值均使用别名
还有为了防止两个相同的类进行拷贝,我们优化一下
Date& operator=(const Date& b)
{
if(*this!=&b)
{
_year = b._year;
_month = b._month;
_day = b._day;
}
return (*this);
}
因为赋值运算符的重载函数是成员默认函数,所以不存在全局的赋值运算符重载,而其他的运算符就存在
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。对于自定义类型就会调用它的赋值运算符重载
这种就是挨个挨个字节拷贝的叫做浅拷贝,如果还要拷贝malloc的空间的叫做深拷贝
3.前置++和后置++重载
首先说一下前置++与后置++的区别,Date d;
d++,返回的是d加1之前的,但d本身加1了
++d返回的是d加1之后的,那么就是返回d
Date operator++()//后置加加
{
Date tmp = (*this);
_day++;
return tmp;
}
Date& operator++()//前置加加
{
_day++;
return (*this);
}
如果仅仅只是这样写的话,函数再调用的时候,是无法区分是前置还是后置的
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
cout << "~Date()" << endl;
}
Date operator++(int)//后置加加
{
Date tmp = (*this);
_day++;
return tmp;
}
Date& operator++()//前置加加
{
_day++;
return (*this);
}
void print()
{
cout << _year << "年" << _month << "月" << _day << "号" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a(1, 1, 1);
Date c(1, 1, 1);
Date b=a++;
Date d = ++c;
b.print();
d.print();
return 0;
}
为了区分前置与后置,我们给后置的运算符重载加上一个int参数,因为这个int参数完全没什么用,只是为了区分,所以不用写参数名字都可以,写了也没错,
a.operator++(1);
a.operator++();
当然如果显示调用的话,就只能这样区分前置和后置了,虽然++是单目操作符,但是这样也可以的原因就是,这就是规定
4.日期类的实现
日期类的实现,我们声明与定义分离来实现
// // 提高效率输入的效率,竞赛
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
这段代码可以提高运行效率,虽然不知道怎么提高的
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
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);
// 日期-日期 返回天数
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
这个是头文件
我们先实现比较类运算符重载,这个只需要实现其中两个,其他的就可以相互调用实现了
这里我们先实现>和==
// >运算符重载
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;
}
// ==运算符重载
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);
}
然后实现一些基础的默认函数
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return (*this);
}
// 析构函数
Date::~Date()
{
cout << "Date::~Date()" << endl;
}
接下来我们开始写日期与int的计算
在此之前我们先实现一个获取天数的函数
//获取某年某月的天数
int GetMonthDay(int year,int month)
{
static int arr[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))))
{
return 29;
}
else
{
return arr[month];
}
}
//日期 += 天数
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);
}
// 日期+天数
Date Date::operator+(int day)
{
Date d = (*this);
d += day;
return d;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
if (_month == 1)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return (*this);
}
// 日期-天数
Date Date::operator-(int day)
{
Date d = (*this);
d -= day;
return d;
}
先说一下+=与+的区别,+=的话会改变this的值,+表达式不会改变this的值,会返回表达式+后的值
再说一下+与-的区别,+补充的的是当前月的天数,而-补充的是上一个月的天数
// 前置++
Date& Date::operator++()
{
(*this) = (*this) + 1;
return (*this);
}
// 后置++
Date Date::operator++(int)
{
Date tmp = (*this);
(*this) = (*this) + 1;
return tmp;
}
// 后置--
Date Date::operator--(int)
{
Date tmp = (*this);
(*this) = (*this) - 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
(*this) = (*this) - 1;
return (*this);
}
这个++前面已经说过很多遍了,这里便不再描述
// 日期-日期 返回天数
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 ret = 0;
while (min != max)
{
ret++;
++min;
}
return ret*(flag);
}
以前cout,cin只能作用于内置类型,但我们可以对cout,cin也进行重载,让它们能够作用于自定义类型,这样的话,就要把cout看作一个参数传进去,对应参数类型要用ostream&。
void operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "号"endl;
}
但这样有个问题,就是因为this永远的默认为第一个参数,所以只能这样使用
d1 << cout;
为了改变这种情况,因为这与我们平时使用的不一样嘛,所以要改变一下,我们只能定义为全局的了
void operator<<(ostream& out, Date d)
{
out << d._year << "年" << d._month << "月" << d._day << "号" << endl;
}
但这样又出现了一个问题,就是不能访问类的私有,为了解决这种情况,我们现在提出一个新方法
就是在类的最前面写声明,而且在函数声明的前面写上friend,这样既表示这是个全局的函数,还表示它是朋友,可以访问私有。这样就可以访问了,但要i注意,friend只能在类里面写,不能在类外面写
cout << d1;
但是还有一个问题,就是不能打印多个对象,因为返回值是void,应该返回cout的,所以再次改进
ostream& operator<<(ostream& out, Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "号" << endl;
return out;
}
就这样我们同理可得cin的重载函数
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
friend istream& operator>>(istream& in, Date& d);
int main()
{
Date d1(2024, 4, 3);
Date d2(2024, 4, 4);
cin >> d1 >> d2;
cout << d1 << d2;
return 0;
}
注意我这里有点小失误,前面的临时参数应该取别名
bool CheckDate()
{
if (_month < 0 || _month>12 || _day <= 0 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
if (!d.CheckDate())
{
cout << "输入非法" << endl;
}
return in;
}
这个函数可以与之结合,防止输入非法
5.const成员
有些时候我们出现这种问题,就是定义了一个const的类,然后去调用相关函数,就会出错
为什么呢,因为this指针的默认类型是Date*,而const Date修饰的变量,它的指针类型默认为const Date*,这样也是为了不改变值,而this指针就是编译器传地址的操作,而且这个操作是默认的,所以这样的话,就会发生权限的放大,会出错,但是this指针的类型为Date*无法改变,怎么办呢
void print()const
{
cout << _year << "年" << _month << "月" << _day << "号"<<endl;
}
就是这样处理,在函数的声明与定义的后面加上
const就表示this指针的类型是const Date型的
其实this指针真正的类型为Dateconst表示this不能改
这样的话就变为const Date*const表示什么都不能改,当只访问this指针时就是用const修饰
6.取地址及const取地址操作符重载
这两个默认成员函数最没用的
直接看代码
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
不用实现,编译器会带的