类与对象(中)
1.类与对象
前言:在我们日常写程序中,有些时候总会忘记初始化,或者程序结束后忘记释放空间,销毁变量等,因此,c++引入了构造函数。
1.1 类的6个默认成员函数
class Date {};
如果一个类中什么成员没有简称为空类,但是每个空类中编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显示实现,编译器会生成的成员函数称为默认成员函数。
1.2 构造函数
1.2.1 初步认识构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特征:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 关于编译器生成的默认成员函数,很多人会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
}
构造函数主要完成初始化任务,从以上来看,构造函数会自动调用我们的初始化函数
1.2.2 构造函数的用法
#include <iostream>
using namespace std;
class Stack
{
public:
//函数名与类名相同
//无返回值
//构造函数可以构成重载
//自动调用构造
//构造函数重载,但是在调用时,如果有无参,也有有参时要注意调用方法
//Stack()
//{
// cout << "Stack()" << endl;
// _a = (int*)malloc(sizeof(int) * 4);
// if (_a == nullptr)
// {
// perror("malloc error");
// return;
// }
// _capacity = 4;
// _top = 0;
//}
Stack(int capacity = 4)
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc error");
return;
}
_capacity = capacity;
_top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
这里调用构造函数有两种方法,一个是有参的,一个是无参的,但是需要注意的是在调用时,要注意调用方法,如果直接调用无参的可能会有歧义,因为跟构造函数冲突,所以,一般使用构造函数是最为高效的。
int main()
{
//Stack s1;调用冲突,重载函数构造函数必须去掉一个才可以
Stack s1(4);
return 0;
}
构造函数的自动调用
class Date
{
public:
//Date()
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
void print()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
void fun()
{
cout << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.print();
return 0;
}
程序运行结构图:
我们可以看出系统自动给我们的年月日是随机数,这其实就是系统自动分配内存时本来就有的数据,编译器在初始化时并没有对我们的数据进行赋值。
我们定义的_year,_month,_day,其实是内置数据类型(语言本身就有的int,char,long之类的),与之相对的是自定义类型(class,struct,Union之类的)。
结论:
- 一般情况下,构造函数都需要我们自己写。
- 编译器对内置类型的初始化————不会对他进行赋值。
- 编译器对自定义类型的初始化————调用自定义
- 注意:
编译器自动生成的条件是我们不写编译器才会自动生成默认的,但我们一旦实现了任意一种构造函数,编译器就不会自动生成了。
我们不能这样去调用一个构造函数或者建立对象:Date d1();。这样程序会报错,谁知道你是不是在声明一个返回值为Date类型的函数呢,这样的问题也被称之为——二义性。
1.3 析构函数
1.3.1 初步认识析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特征:
- 析构函数名在类名前加上字符~。
- 无参数无返回值类型。
3.一个类只有能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4.对象生命周期结束时,C++编译系统自动调用析构函数。
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;
}
以上看出对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
1.3.2 析构函数用法
自动形成的析构函数
- 内置类型成员不做处理
- 自定义类型会去调用他的析构函数
什么时候需要手动调用析构函数,什么时候自动调用析构函数
-
- 一般情况下,有动态申请资源就需要显示写析构函数释放资源。
手动调用析构函数的情况:
自动调用析构函数的情况:
我们可以看出,此时系统自动调用析构函数并没有将空间销毁。
-
- 没有动态申请的资源,不需要写析构函数
class Date
{
public:
//构建函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
当程序结束时,栈区的东西就会全部销毁,所以这里不需要我们手动调用析构函数。
-
- 需要释放资源的成员都是自定义类型,不需要写析构。
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
{
perror("malloc error");
return;
}
_capacity = 4;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MeQueue
{
Stack s1;
};
int main()
{
MeQueue m1;
MeQueue m2;
}
1.4 拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
- 拷贝构造函数是构造函数的一个重载形式
- 拷贝构造函数的参数必须是一个类类型引用,如果使用传值方式编译器会直接报错,如果不报错就会无限的递归调用下去。
不是引用的情况
规定:
内置类型可以直接拷贝。
自定义类型必须调用拷贝构造完成拷贝
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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,1,1);
Date d2(d1);
return 0;
}
const是不可省略的,如果省略const会出现以下问题
如果交换代码写反,那么就不会完成我们需要的拷贝操作,反而会给我们赋值成随机值。
- 若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
3.1 内置类型成员完成值拷贝/浅拷贝。
3.2 自定义类型成员会调用他的拷贝构造
自动完成构造函数拷贝(内置类型)
情况一:
class Date
{
public:
Date(int year = 1 , int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//可以不写拷贝函数,默认生成的拷贝构造就可以用
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,1,1);
Date d2(d1);
//d1.print();
return 0;
}
这里我们看到,编译器完成了在没有拷贝构造函数的情况下,自动完成拷贝工作。
情况二:
浅拷贝
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
{
perror("malloc error");
return;
}
_capacity = 4;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
这时,我们发现数组a共用一个地址,显然这不是安全的,当程序完成进行销毁时就会报错,因为同一个空间不能销毁两次;还有就是改变其中一个数组,另一个就会变。
解决办法:
深拷贝
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * 4);
if (_a == nullptr)
{
perror("malloc error");
return;
}
_capacity = 4;
_top = 0;
}
//深度拷贝
//必须自己实现深度拷贝
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("maloc error");
return;
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
这样一来,空间就分开了,各是各的,互不干扰。
自定义类型
class Stack
{
public:
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("maloc error");
return;
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MeQueue
{
Stack s1;
};
int main()
{
MeQueue m1;
MeQueue m2;
}
自定义类型会调用自定义的拷贝函数,是不需要自己写的
1.5 运算符重载
当我们平时写代码对日期就行比较时,基本都是这样操作二点:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = _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;
}
bool Less2(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,4,26);
Date d2(2023,6,21);
Less(d1, d2);
cout << Less(d1, d2) << endl;
cout << Less2(d1, d2) << endl;
return 0;
}
cout << Less(d1, d2) << endl;
cout << Less2(d1, d2) << endl;
这样写不仅我们很难看出这时谁比较谁,读起代码来也是比较麻烦呢,这时我们c++就引出了赋值运算符重载(operator)。
c++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
函数名关键字为operator
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: .
注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
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;
}
return false;
}
//与上面功能等价
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;
}
bool Less2(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;
}
//与上面功能等价
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;
}
return false;
}
int main()
{
Date d1(2023,4,26);
Date d2(2023,6,21);
cout << Less(d1, d2) << endl;
cout << (d1 > d2) << endl;//显然可读性是提高的,转换成operator>(d1,d2)
cout << Less2(d1, d2) << endl;
cout << (d1 < d2) << endl;//显然可读性是提高的,转换成operator<(d1,d2)
return 0;
}
但是这里有个问题是如果类变私有,这里的比较函数就会访问失败,那么我们可以将他放到类里头来解决。
class Date
{
public:
Date(int year = 1, int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = _day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
bool operator>(const Date& x)
{
if (_year > x._year)
{
return true;
}
else
if (_year == x._year && _month > x._month)
{
return true;
}
else
if (_year == x._year && _month == x._month && _day > x._day)
{
return true;
}
return false;
}
bool operator<(const Date& x)
{
if (_year < x._year)
{
return true;
}
else
if (_year == x._year && _month < x._month)
{
return true;
}
else
if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,4,26);
Date d2(2023,6,21);
cout << (d1 > d2) << endl;//转换成d1.operator(d2)
cout << (d1 < d2) << endl;//转换成d1.operator(d2)
return 0;
}
1.5.1 赋值运算符重载
1. 赋值重载
赋值运算符重载格式:
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this:要符合连续赋值的含义
#include <iostream>
using namespace std;
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(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 4, 26);
Date d2(2023,6,21);
//已经存在的两个对象之间复制拷贝 -- 运算符重载函数
d1 = d2;
//用一个已经存在的对象初始化另一个对象 -- 构造函数
Date d3(d1);
return 0;
}
在每次赋值的时候都会重复调用拷贝构造函数
还有一个问题就是如果出现这样的赋值d5 = d4 = d1
时,如果按照上方的程序来运行就会出现报错,因为赋值运算符重载函数的返回值为void。
如果要改变上方的两个问题就要修改赋值运算符的返回类型和调用类型。
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
此时我们发现,连续赋值的效率提高,而且不重复调用拷贝构造函数了,这是因为我们每次都是已引用的方式来进行返回,没有了返回值的拷贝,自然效率就会高很多。
赋值运算符只能重载成类的成员函数不能重载成全局函数
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;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
赋值运算符如果不显示实现,编译器会生成一个默认的,此时用户如果在类外自己实现一个全局的赋值运算符重载,就和编译器在类中自动生成的赋值运算符重载起冲突,所以就会报错,所以,赋值运算符重载只能是类的成员函数。
用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
2. 比较符重载
//头文件声明
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
bool operator<(const Date& x);
bool operator==(const Date& x);
bool operator<=(const Date& x);
bool operator>(const Date& x);
bool operator>=(const Date& x);
bool operator!=(const Date& x);
int _year;
int _month;
int _day;
};
//源文件定义
#include "Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
bool Date::operator<(const Date& x)
{
if (_year < x._year)
{
return true;
}
else
if (_year == x._year && _month < x._month)
{
return true;
}
else
if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}
return false;
}
bool Date::operator==(const Date& x)
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
bool Date::operator<=(const Date& x)
{
return *this < x || *this == x;
}
bool Date::operator>(const Date& x)
{
return !(*this <= x);
}
bool Date::operator>=(const Date& x)
{
return !(*this < x);
}
bool Date::operator!=(const Date& x)
{
return !(*this == x);
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023,6,21);
cout << "d1<d2 --- "<<(d1 < d2) << endl;
d1 = d2;
cout << "d1<=d2 --- "<<(d1 <= d2) << endl;
cout << "d1>=d2 --- " << (d1 >= d2) << endl;
cout << "d1>d2 --- " << (d1 > d2) << endl;
cout << "d1!=d2 --- " << (d1 != d2) << endl;
cout << "d1==d2 --- " << (d1 == d2) << endl;
return 0;
}
3.前置++后置++重载
//头文件声明
#include <iostream>
using namespace std;
class Date
{
public:
//获取指定月份的天数
int GetMonthDay(int year, int month);
Date& operator+=(int day);
Date operator+(int day);
//++d1
//d1++
//直接按特性重载,无法区分
//特殊处理,使用重载区分,后置++重载增加一个int参数跟前置构造函数重载进行区分
//前置++,返回+1之后的结果
//注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++();//前置 d1.operator++(&d1);
//后置++:
//为了让前置++和后置++形成正确的重载,C++规定:后置++重载
//时多增加一个int类型的参数,但调用函数时该参数不用传递
//编译器自动传递
//注意:
//后置++先使用后+1,所以要返回+1时的旧值,所以,要将this临时拷贝一份来返回
Date operator++(int);//后置 d1.operator++(&d2,0);
private:
int _year;
int _month;
int _day;
};
//源文件定义
int Date::GetMonthDay(int year, int month)
{
static int daysArr[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 daysArr[month];
}
Date& Date::operator+=(int day)
{
_day += day;
//将多余的天数或者月数向上排
while (_day>GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
//方法一:
tmp += day;
return tmp;
//方法二:
//tmp._day += day;
//while (tmp._day > GetMonthDay(tmp._year, tmp._month))
//{
// tmp._day -= GetMonthDay(tmp._year, tmp._month);
// ++tmp._month;
// if (tmp._month == 13)
// {
// ++tmp._year;
// tmp._month = 1;
// }
//}
//return tmp;
}
//前置++
Date& Date::operator++()
{
*this = *this + 1;
return *this;
}
//后置++
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023,6,21);
d1 += 5;
Date d3 = d2 + 10;
Date d4 = d1++;
++d1;
return 0;
}
初始后的初始值
+=后
因为+不改变目标值,一般就是做赋值操作
后置++是执行完后目标变量才会改变值,所以,我们这里看到,是目标程序结束后才会++
前置++直接赋值就好
4.前置–后置–重载
-= += -
运算符重载
//头文件声明
#include <iostream>
using namespace std;
class Date
{
public:
void Print();
Date& operator-=(int day);
Date operator-(const int day);
private:
int _year;
int _month;
int _day;
};
//源文件定义
#include "Date.h"
Date& Date::operator-=(int day)
{
//2023.4.26
//天数 - 100 得到最后的日期为
//2023.4.-74
//这时我们发现天数已经变为负的,根据这个条件我们来计算他对应的日期
_day -= day;
while (_day <= 0)
{
--_month;
//如果发现日期变为0,那么就要让年减一
if (_month == 0)
{
_month = 12;
--_year;
}
//取对应的上个月的天数相加,直到day变为正数位置
_day += GetMonthDay(_year, _month);
}
return *this;
}
void Date::Print()
{
cout << _year << "." << _month << "." << _day << endl;
}
Date Date::operator-(const int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
d1 -= 100;
d1.Print();
}
考虑一下如果我们将天数换成负数,那么程序会出问题吗?
我们发现这时计算出的日期就会出错,原因就是我们忽略了天数也是可以输入负数的,当减等于负数的时候,就会变成负负得正,加一个天数,所以就会出现上面的情况,接下来我们来解决一下这个情况
Date& Date::operator-=(int day)
{
//当发现需要计算的日期为负数时,就相当于是+=day
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
这里我们发现 -= 这种情况,那么 += 也是会发生同样的情况的,所以我们也是需要对 += 进行修改的
当 += 一个日期天数时,其实就相当于 -= ,所以我们只需要将天数变正,然后调用 -= 即可。
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;
}
以上操作我们都是对天数进行操作,那么我们可以对年月日整体操作吗,接下来,我们学习计算一下两个年月日之间相差几天。
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 (min != max)
{
++min;
++n;
}
return n * flag;
}
选取天数最少的一天,定义一个变量来记录两个日期相差的天数,直到两个日期相等。
5.流插入重载
//头文件声明
#include <iostream>
using namespace std;
class Date
{
public:
//流插入不能写成成员函数
//因为Date对象默认占用第一个参数,就是做了左操作数
//不符合使用习惯,写出来就是下面这个样子
//d1 << cout; d1.operator(cout);
void operator<<(ostream& out);
private:
int _year;
int _month;
int _day;
};
//源文件定义
#include "Date.h"
void Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
d1 << cout;
//cout << d1;此时就会报错
//因为调用错误 cout.d1,与我们重载的流插入的实参正好相反
}
解决方法有两种:
- 1.设置成全局函数
//头文件声明
#include <iostream>
using namespace std;
class Date
{
public:
//const修饰this指针
int GetYear()const
{
return _year;
}
int GetMonth()const
{
return _month;
}
int GetDay()const
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out,const Date& d);
//源文件定义
#include "Date.h"
void operator<<(ostream& out,const Date& d)
{
out << d.GetYear() << "年" << d.GetMonth() << "月" << d.GetDay()<< "日" << endl;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
cout << d1;//operator<<(cout,d1);
}
- 2.设置成友缘函数(推荐方法)
//头文件声明
#include <iostream>
using namespace std;
class Date
{
//友缘函数
friend ostream& operator<<(ostream& out, const Date& d);
public:
//const修饰this指针
int GetYear()const
{
return _year;
}
int GetMonth()const
{
return _month;
}
int GetDay()const
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d);
//源文件定义
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d.GetMonth() << "月" << d._day << "日" << endl;
return out;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
cout << d1;
cout << d1 << d2;
}
6.流提取运算符重载
//头文件声明
#include <iostream>
using namespace std;
class Date
{
//友缘函数
friend ostream& operator<<(ostream& out, const Date& d);
public:
friend istream& operator>>(istream& in, Date& d);
//const修饰this指针
int GetYear()const
{
return _year;
}
int GetMonth()const
{
return _month;
}
int GetDay()const
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
//源文件定义
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d.GetMonth() << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in,Date& d)
{
//in >> d._year >> d._month >> d._day;
//以上输入不会判断有效日期,接下来我们来
int year, month, day;
year = month = day = 0;
in >> year >> month >> day;
if (month > 0 && month < 13 && day > 0 && day <= d.GetMonthDay(year,month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
return in;
}
int main()
{
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
cout << d1;
cin >> d1;
cout << d1;
}
1.6 const成员
const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
int main()
{
Date d1(2023, 4, 26);
const Date d2(2023, 6, 21);
d1.Print();
d2.Print();//d2.Print(&d2);
//这里属于权限放大所以会报错,所以我们接下来就要学习一下怎么使用const
}
void Print()const;
void Date::Print()const
{
cout << _year << "." << _month << "." << _day << endl;
}
int main()
{
Date d1(2023, 4, 26);
const Date d2(2023, 6, 21);
d1.Print();
d2.Print();//d2.Print(&d2);
}
这时使用const修饰就不会出现放大错误了
结论:
成员函数后面加const以后,普通和const对象都可以调用
也不是所有函数后边都可以加const,要修改的对象成员变量的函数后不可加const
只要成员函数内部不修改成员变量,都应该加const,这样const对象和普通 对象都可以调用。
1.7 取地址及const取地址操作符重载
class Date
{
public:
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
Date d2;
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
默认调用的返回地址
我们也可以重新定义他的返回地址格式
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; // 日
};
int main()
{
Date d1;
Da te d2;
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
在这里我们也可以将地址设置成返回空。
这两个默认构造成员函数一般不用重新定义,编译器默认会自动生成的。