目录
- 1.运算符重载
- ①运算符重载的概念
- ②日期类+和+=运算符重载
- 2.赋值运算符重载
- 3. 流插入运算符<<重载
- 4.Date类实现
- 5.const成员
- 6.取地址及const取地址操作符重载
1.运算符重载
大家有没有想过内置类型可以使用的运算符是否自定义类型的成员变量也可以使用呢?
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day=1)
{
cout << "Date" << endl;
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
正常情况下,让我们比较日期类成员变量的大小(即日期的大小),我们可以用一个函数实现!
//需要把内置类型的成员变量权限设置为公有
bool Less(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;
}
return false;
}
int main()
{
Date d1(2023, 5, 23);
Date d2(2023, 4, 26);
cout << Less(d1, d2) << endl;
return 0;
}
代码运行的结果为:
①如果实现比较日期大小函数的人代码素养比较高,他会将这个函数命名为Less
,我们看到之后根据结果会知道该函数是判断日期大小的函数;但如果有人将函数名随便命名为Func
,这时我们便不得而知函数的功能,造成可读性差的结果。②相比内置类型直接写成i<j
,自定义类型还需要进行传参,相对而言比较麻烦。有没有什么办法能使自定义类型像内置类型一样呢?答案:有的。
①运算符重载的概念
C++为了增强代码可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
也具有返回类型、函数名字以及参数列表,其返回值与参数列表的普通函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回类型operator操作符(参数列表)
性质: 1.不能连接其他符号来创建新的操作符:比如operator@
2.重载运算符必须有一个自定义类型的参数,如果有两个以上的参数,其他的参数可以为自定义类型、内置类型
3.用于内置类型的运算符,其含义不能改变,例如:内置类型的+,不能改变其含义
4..*
(访问、解引用)、::
(域访问限定符)、sizeof
(计算大小) 、?;
(三目操作符)、.
(成员访问)
注意: 以上5个运算符不能重载,这个经常出现在笔试选择题中
使用运算符重载实现日期类比较大小的函数如下:
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 == _month && _day < d._day)
{
return true;
}
return false;
}
注意: 根据运算符重载的性质,运算符重载有两个及两个以上的参数,有一个是在参数列表里面的形参const Date& d
函数中有一个隐藏的this
指针,操作符是几个操作数,就有几个参数
调试代码如下:
void Test()
{
Date d1(2023, 5, 23);
Date d2(2023, 4, 26);
d1 < d2;
d1.operator<(d2);
//底层汇编代码是一样的
cout << (d1 < d2) << endl;
cout << (d1.operator<(d2)) << endl;
}
汇编代码:
d1 < d2;
00F32281 lea eax,[d2]
00F32284 push eax
00F32285 lea ecx,[d1]
00F32288 call Date::operator< (0F312F3h)
d1.operator<(d2);
00F3228D lea eax,[d2]
00F32290 push eax
00F32291 lea ecx,[d1]
00F32294 call Date::operator< (0F312F3h)
代码运行的结果:
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);
}
②日期类+和+=运算符重载
如果我们知道某个日期经过50天、100天之后的日期是多少?我们使用当前日期,当前月的天数满了,当前月份进位加1,年份进位加1,一年中每个月天数又不一样,而特殊的二月份每逢闰年天数加一,所以我们使用一个函数判断。
int Date::GetMonth(int year, int month)
{
static int Array[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;
}
else
{
return Array[month];
}
}
+=运算符重载实现
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonth(_year, _month))
{
_day -= GetMonth(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date
类+=
加完之后,Date
类自身的日期会发生改变,类比内置类型int i=10;i+=10
;+=
运算符重载函数需要返回值,需要做到像内置类型int i=0,j=6;j+=i+=10;
一样。
+运算符重载实现
Date Date::operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonth(tmp._year, tmp._month))
{
tmp._day -= GetMonth(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
+
运算符重载之后,Date
类自身的日期不变,并且返回Date
类加上day
天数的结果进行赋值,类比内置类型+
运算操作int i;int j=i+1;
复用+=运算符实现+运算符重载
Date Date::operator+(int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
复用+运算符实现+=运算符重载
//利用+重载改造+=重载
Date& Date::operator+=(int day)
{
*this = *this + day;
return *this;
}
建议使用+=运算符重载对+进行改造,拷贝较少,创建对象较少,提高效率。
测试代码如下:
void TestDate2()
{
Date d1(2023, 4, 26);
d1.Print();
d1.operator+=(100);
d1.Print();
//Date d2;
//d2=d1.operator+(100);
Date d2(d1 + 100);
d2.Print();
Date d3 = d1 + 100;//等价Date d2(d1 + 100)
d3.Print();
代码运行的结果为:
2.赋值运算符重载
1.赋值运算符重载格式
①参数类型:const Date&
,传递引用可以提高效率
②返回类型:Date&
返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
③检测是否自己给自己赋值
④返回*this
:要复合连续赋值的含义
2.赋值运算符只能重载成类的成员函数,不能直接定义成全局函数,因为会与类里面生成的赋值函数冲突,且不具备构成函数重载的条件
3.赋值运算符重载函数,用户在没有显示实现时,编译器会默认生成一个默认赋值运算符重载函数以值的方式进行逐字节拷贝,注意:内置类型成员变量是直接赋值的,而自定义类型成员需要调用对应的类赋值运算符重载完成赋值。
Date
和MyQueue
不需要我们实现运算符重载,Stack需要我们自己实现,因为默认生成值拷贝。
Date& operator=(const Date& d)
{
cout << "Date& operator=(const Date& d)" << endl;
//自己给自己赋值的情况
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//int i,j,k;
//i=j=k
//之所以需要返回值(即函数类型不是void),针对像内置类型可以赋值的情况
//自定义类型也可以做到连续赋值,如果返回类型是void,自定义类型不可能连续赋值
return *this;
}
一个栗子:
//赋值运算符重载
//1.赋值运算符重载的格式
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数
//类外面不能访问私有和保护、类里面可以访问私有和保护
Date(int year = 1900, int month = 1, int day = 1)
{
cout << "Date(int year = 1900, int month = 1, int day = 1)" << endl;
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
cout << "Date& operator=(const Date& d)" << endl;
//自己给自己赋值的情况
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
int i,j,k;
//i=j=k
//之所以需要返回值(即函数类型不是void),针对像内置类型可以赋值的情况
//自定义类型也可以做到连续赋值,如果返回类型是void,自定义类型不可能连续赋值
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//调用自己写构造函数
Date d1(2023, 4, 26);
Date d2(2023, 5, 24);
//已经存在的两个对象之间的复制拷贝--运算符重载
d1 = d2;
//用一个已经存在的对象初始化另一个对象--拷贝构造函数
Date d3(d1);
//调用构造函数
Date d4, d5;
//调用赋值运算符重载函数
d5 = d4 = d1;//连续调用
return 0;
}
代码运行的结果为:
前置++与后置++运算符重载
//前置++,返回++之后的结果
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++,返回++之前的结果
//增加int参数不是为了接收值,而是为了占位,与前置++构成重载
Date Date::operator++(int)
{
Date tmp = *this;
tmp += 1;
return tmp;
}
测试代码为:
void TestDate3()
{
Date d1(2023, 4, 26);
//都要++,前置++返回++之后的对象,后置++返回++之前的对象
d1.Print();
d1++;//d1.operator(int)
d1.Print();
++d1;//d1.operator()
d1.Print();
}
代码运行的结果为:
3. 流插入运算符<<重载
前面我们所学习的流插入的操作符,会自动识别数据类型,是标准库里帮我们已经实现好的,但仅限于识别内置类型;自定义类型需要我们自己使用运算符重载实现。
//类里面的声明
ostream& operator<<(ostream& out);
//定义
ostream& Date::operator<<(ostream& cout)
{
cout << _year << "-" << _month << "-" << _day << endl;
return cout;
}
测试代码:
void Test1()
{
Date d1(2023, 7, 23);
cout << d1 << endl;
}
代码调试运行的结果:
根据类成员函数定义,第一个参数为
*this
,cout << d1 << endl;
参数顺序不对,d1 << cout;
不符合使用习惯,所以编译器会报错。
正确实现方式
//类里面的
friend ostream& operator<<(ostream& out, const Date& d);
//定义
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << "-" << d._month << "-" << d._day << endl;
return cout;
}
测试代码:
void Test1()
{
Date d1(2023, 7, 23);
cout << d1 << endl;
}
代码运行的结果如下:
把流插入运算符重载函数声明在类里面,使用
friend
定义为友元函数,并可以使用Date
类的私有成员变量,类比输出多个内置类型的数据类型,所以需要返回cout
。
4.Date类实现
Date.h文件
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
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;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//<运算符重载
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 GetMonth(int year, int month);
//+=运算符重载
Date& operator+=(int day);
//+运算符重载
Date operator+(int day);
//后置++
Date& operator++();
//前置++
Date operator++(int);
//-=运算符重载
Date& operator-=(int day);
//-运算符重载
Date operator-(int day);
//后置--运算符重载
Date& operator--();
//前置--
Date operator--(int);
//日期天数
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
Date.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//bool Date::operator<(const Date& d)
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 == _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 || *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);
}
int Date::GetMonth(int year, int month)
{
static int Array[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;
}
else
{
return Array[month];
}
}
//+=加完之后,自身的值发生改变
//+=需要返回值,需要做到像内置类型int i=0,j=6;j+=i+=10;一样,
//类比内置类型int i=1;i++;
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonth(_year, _month))
{
_day -= GetMonth(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
//+加完之后,自变量的值不变,进行赋值,类比内置类型,int i;i+1;
//Date Date::operator+(int day)
//{
// Date tmp = *this;
// tmp._day += day;
// while (tmp._day > GetMonth(tmp._year, tmp._month))
// {
// tmp._day -= GetMonth(tmp._year, tmp._month);
// tmp._month++;
// if (tmp._month == 13)
// {
// tmp._year++;
// tmp._month = 1;
// }
// }
// return tmp;
//}
//利用+=重载改造+重载
Date Date::operator+(int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
//利用+重载改造+=重载
//Date& Date::operator+=(int day)
//{
// this = *this + day;
// return *this;
//}
//建议使用+=运算符重载对+进行改造,拷贝较少,创建对象较少,提高效率
//前置++,返回++之后的结果
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++,返回++之前的结果
//增加int参数不是为了接收值,而是为了占位,与前置++构成重载
Date Date::operator++(int)
{
Date tmp = *this;
tmp += 1;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
_year--;
}
int day1= GetMonth(_year, _month);
_day += day1;
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
Date tmp = *this;
tmp -= 1;
return tmp;
}
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
++min;
++n;
}
return flag * n;
}
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << "-" << d._month << "-" << d._day << endl;
return cout;
}
istream& operator>>(istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month < 13 && day <=
d.GetMonth(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
return in;
}
5.const成员
将
const
修饰的“成员函数”称为const
成员函数,const
修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
我们不希望改变类成员函数的私有成员变量,就需要使用const修饰成员函数。
#include<iostream>
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
{
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, 7, 23);
d1.Print();
const Date d2(2022, 7, 23);
d2.Print();
}
代码运行结果:
①不是所有成员函数都加
const
,要修改的对象成员变量的函数不能加const
修饰;②只要成员函数内部不修改成员修改变量,都应该加const
修饰,这样const
对象和普通对象都可以调用。
6.取地址及const取地址操作符重载
class Date
{
public:
Date* operator&()
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&()const
{
cout << "const Date* operator&()const" << endl;
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定内容。