✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++
🔥<3>创作者:我的代码爱吃辣
☂️<4>开发环境:Visual Studio 2022
💬<5>前言:C++类中一共有六个默认成员函数,今天我们先来将剩下的赋值重载,和取地址重载,以及实现Date类。
目录
一.运算符重载
二.赋值运算符重载格式:
三.前置++和后置++重载
四.const成员
五.取地址及const取地址操作符重载
六. cout 输出类信息
七.Date类实现
(1)Date类的主要功能模块
(2)日期大小比较
(3)日期+天数,日期+=天数,++日期,日期++
(4)日期-日期,日期-天数,日期-=天数
测试
一.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)。
假如我们需要实现一个日期加上一个天数得到一个新的日期,此时按照我们以前的思考方式,是实现一个函数,但是利用运算符重载,会变得更加直观明了
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void show()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int _year;
int _month;
int _day;
};
//函数判断日期相等
bool DateEqual(const Date& d1, const Date& d2)
{
return d1._year == d2._year &&
d1._month == d2._month &&
d1._day == d2._day;
}
//运算符重载的定义 使用关键字 operator 【运算符】
bool operator==(const Date& d1 ,const Date& d2)
{
return d1._year == d2._year &&
d1._month == d2._month &&
d1._day == d2._day;
}
int main()
{
Date d2(2022, 4, 23);
Date d1(2022, 1, 13);
//函数使用
if (DateEqual(d1, d2))
cout << "true" << endl;
else
cout << "false" << endl;
//运算符重载的使用
if (d1 == d2)
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
但是一般我们的成员变量都是私有的,如果是私有的我们就不能直接访问,我们通过添加 get函数来解决,或者通过将运算符重载的函数写道类的里面,变成成员函数,这里我们就直接封装到类里面:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& date)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator ==(const Date& date)
{
return _year == date._year &&
_month == date._month &&
_day == date._day;
}
void show()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d2(2022, 1, 13);
Date d1(2022, 1, 13);
//运算符重载的使用
if (d1 == d2)
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
注意:在将运算符重载写成成员成员函数时,重载运算符只需要写一个参数即可,因为有一个参数就是本对象本身,就是隐藏在成员函数列表的this指针。
二.赋值运算符重载格式:
1.参数类型:const T&,传递引用可以提高传参效率
2.返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值
3.返回*this :要复合连续赋值的含义
class Date
{
public:
Date(int year = 1900, 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& operator=(const Date& d)
{
//如果不是自身赋值,即d1=d1,才需要成员变量对用赋值。
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void show()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date1;
Date date2(2023, 2, 7);
date1.show();
//因为有返回值就可以实现链式访问
(date1 = date2).show();
return 0;
}
4. 赋值运算符只能重载成类的成员函数不能重载成全局函数
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;
}
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。
5.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
cout << "Time:operator=" << endl;
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void show()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 基本类型(内置类型)
int _year ;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
d1.show();
Date d2(2023,2,7);
d1 = d2;
d1.show();
return 0;
}
注意:内置类型成员变量是直接赋值的,而自定义类型成员变.量需要调用对应类的赋值运算符
重载完成赋。
但是对于有空间申请的空间是需要,我们不能光靠编译器自己生成的赋值运算符重载的:
class A
{
public:
A()
{
a = 10;
arr = (int*)malloc(sizeof(int) * a);
for (int i = 0; i < a; i++)
{
arr[i] = i;
}
}
void show()
{
for (int i = 0; i < a; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int* arr;
int a;
};
int main()
{
A a;
A b;
a.show();
b.show();
b = a;
//修改b对象的arr数组
for (int i = 0; i < b.a; i++)
{
b.arr[i] = 0;
}
//观察两个对象中arr的元素
b.show();
a.show();
return 0;
}
我们只是修改了b对象的 arr 数组,但是a对象的arr数组也被跟着修改了。
具体的情况是:
三.前置++和后置++重载
我们知道前置++和后置++唯一的区别就是,返回值不同。前置++返回++之后的结果,前置++返回++之后的结果。那么运算符重载以后也应该支持前置++和后置++的特性。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
void show()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 1);
d1.show();
Date d2 = d1++;
d2.show();
d1.show();
d2 = ++d1;
d1.show();
d2.show();
return 0;
}
四.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
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 ConstPrint() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.ConstPrint();
return 0;
}
思考下面的几个问题:
1. const对象可以调用非const成员函数吗?解答:const对象不可以调用非const成员函数,因为非const成员函数的*this也是没有经过const修饰的,如果调用了const成员函数,就相当于权限放大。
2. 非const对象可以调用const成员函数吗?解答:非const对象可以调用const成员函数,因为由const修饰的成员函数实际上修饰的就是*this,相当于权限缩小,是允许的。
3. const成员函数内可以调用其它的非const成员函数吗?解答:const成员函数不可以调用其他的非const成员函数,也是一个权限放大的例子。
4. 非const成员函数内可以调用其它的const成员函数吗?解答:非const成员函数可以调用其他的const成员函数,也是一个权限缩小的例子。
五.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date date;
cout << &date << endl;
return 0;
}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return (Date*)0x00000001;
}
const Date* operator&()const
{
return (Date*)0x0000250;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
const Date date(2022,10,1);
Date date1(date);
cout << &date << endl;
cout << &date1 << endl;
return 0;
}
六. cout 输出类信息
cout是类 ostream 的对象,表示面向窄字符(字符类型)的标准输出流。它对应于 C 流标准输出。
作为 ostream 类的对象,可以使用插入运算符(运算符<<)将字符作为格式化数据写入其中,也可以使用成员函数(如 write)将字符作为无格式数据写入其中。
对象在带有外部链接和静态持续时间的头文件声明<iostream>:它持续整个程序的持续时间。
我们可以将ostream的对象cout,也作为运算符重载的参数:
class Date
{
public:
Date(int year = 1900, 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;
};
int main()
{
Date date(2023, 2, 8);
//按照运算符左边到右边是参数从左往右对应
//成员函数第一个参数就是this指针,所以<<运算符左边就是date对象
date << cout;
//等价于
date.operator<<(cout);
return 0;
}
但是 date<<cout ,非常不符合我们的使用习惯,有没有一种办法可以实现 cout << date 呢?造成这种情况的原因是成员函数的第一个参数默认是 this 指针,如果我们不将 << 设计为成员函数是不是就可以解决这个问题呢?
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
//<<运算符重载
void operator<<(ostream& out, Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
Date date(2023, 2, 8);
//按照运算符左右参数,分别对应<<运算符重载从左往右的参数
cout << date;
//等价于
operator<<(cout, date);
return 0;
}
这就又有一个新的问题,成员变量私有化,不允许外部函数直接访问,面对这个问题,我们有两种解决方案:
1.设置get函数
设计成员函数用于返回私有成员变量的值,供外部读取。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//Get函数,用于外部读取私有成员变量
int& Get_year()
{
return _year;
}
int& Get_month()
{
return _month;
}
int& Get_day()
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
//<<运算符重载,为了支持连续访问,加上返回值
otsream& operator<<(ostream& out, Date& d)
{
out << d.Get_year() << "/" << d.Get_month() << "/" << d.Get_day() << endl;
return out;
}
int main()
{
Date date(2023, 2, 8);
//按照运算符左右参数,分别对应<<运算符重载从左往右的参数
cout << date;
//等价于
operator<<(cout, date);
return 0;
}
2.设计友元函数
只需在将函数声明写在Date类的内部,并在函数的前面加上关键字 friend,就可以和类建立友好关系,友元函数可以像成员函数一样随意访问成员变量。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//友元函数
friend void operator<<(ostream& out, Date& d);
private:
int _year;
int _month;
int _day;
};
//<<运算符重载
void operator<<(ostream& out, Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
Date date(2023, 2, 8);
//按照运算符左右参数,分别对应<<运算符重载从左往右的参数
cout << date;
//等价于
operator<<(cout, date);
return 0;
}
七.Date类实现
前面一直在用Date类举例子,这次我们来全面实现一下,练习我们之前学过的类与对象的姿势。
我们严格按照开发标准来实现Date类:
(1)Date类的主要功能模块
//Date.h文件
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, Date& d);
public:
Date(int year, int month, int day );
//日期比较大小
bool operator>(const Date& date);
bool operator >= (const Date& date);
bool operator <(const Date& date);
bool operator<=(const Date& date);
bool operator ==(const Date& date);
//日期+天数
Date operator+(int x);
//日期+=天数
Date& operator+=(int x);
//日期-日期=天数
int operator-(const Date& date);
//日期-天数=日期
Date operator-(int x);
//日期-=天数
Date operator-=(int x);
//Date++
Date operator++(int);
//++Date
Date& operator++();
private:
int GetMonthDay(int year, int month);//获取每月天数
int _year;//年
int _month;//月
int _day;//日
};
//Date.cpp
//构造函数定义
Date::Date(int year , int month, int day)
{
assert(month <= 12 && day <= this->GetMonthDay(year, month));
_year = year;
_month = month;
_day = day;
}
//输出日期:年/月/日(友元函数)
void operator<<(ostream& out, Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
}
(2)日期大小比较
//Date.cpp
bool Date::operator>(const Date& date)
{
if (_year != date._year)//如果年不相等,直接判断出结果
return _year > date._year;
if (_month != date._month)//年相等的时候,如果月不相等,直接判断出结果
return _month > date._month;
if (_day != date._day)//年.月相等的时候,如果日不相等,直接判断出结果
return _day > date._day;
}
bool Date::operator >= (const Date& date)
{
//>=就是或上一个相等
return *this > date || *this == date;
}
bool Date::operator ==(const Date& date)
{
//相等需要年月日都相等
return _year == date._year && _month == date._month && _day == date._day;
}
bool Date::operator <(const Date& date)
{
// >=取反
return !(*this >= date);
}
bool Date::operator<=(const Date& date)
{
//<或上==
return *this < date || *this == date;
}
(3)日期+天数,日期+=天数,++日期,日期++
int Date::GetMonthDay(int year, int month)
{
int montharr[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)
{
return 29;
}
return montharr[month];
}
Date Date::operator+(int x)
{
Date tmp(*this);
if (x < 0)
{
tmp -= -x;
return tmp;
}
tmp += x;
return tmp;
}
//先将天数全部加上去,然后依次减去当月天数,并判断年月的变换。
Date& Date::operator+=(int x)
{
if (x < 0)
{
*this -= -x;
return *this;
}
_day += x;
while (_day > GetMonthDay(_year,_month))
{
_day -= this->GetMonthDay(_year,_month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//Date++(后置++)
Date Date::operator++(int)
{
Date temp(*this);
*this+=1;
return temp;
}
//++Date(前置++)
Date& Date::operator++()
{
*this += 1;
return *this;
}
(4)日期-日期,日期-天数,日期-=天数
//计数计算,一天一天加,直到日期相等。
int Date::operator-(const Date& date)
{
int count = 0;
Date bigdate = *this;
Date smalldate = date;
if (bigdate < smalldate)
{
Date tmp = bigdate;
bigdate = smalldate;
smalldate = tmp;
}
while (!(smalldate == bigdate))
{
smalldate++;
count++;
}
return count;
}
//日期-天数,先算整数月的减法,直到剩下的天数,小于本月天数,
//加上第一个月,和最后一个月的剩下的天数计算
Date Date::operator-(int x)
{
Date tmp(*this);
if (x < 0)//如果天数小于0,就相当于加上天数
{
tmp += -x;
return tmp;
}
tmp._month--;
if (tmp._month == 0)
{
tmp._month = 12;
tmp._year--;
}
tmp._day = tmp.GetMonthDay(tmp._year, tmp._month);
while (x > tmp.GetMonthDay(tmp._year, tmp._month))
{
x -= tmp.GetMonthDay(tmp._year, tmp._month);
tmp._month--;
if (tmp._month == 0)
{
tmp._month = 12;
tmp._year--;
}
tmp._day = tmp.GetMonthDay(tmp._year, tmp._month);
}
tmp._day -= x;
return tmp + this->_day;
}
//不仅计算返回,而且修改当前对象
Date Date::operator-=(int x)
{
if (x < 0)
{
*this += -x;
return *this;
}
*this = *this - x;
return *this;
}
测试