目录
1.类的6个默认成员函数
2..构造函数
2.1概念
2.2 特征
3.析构函数
3.1 概念
3.2 特性
4.拷贝构造函数
4.1 概念
4.2 特征
5.赋值运算符重载函数
5.1 运算符重载(是否重载这个运算符是看这个运算符对这个类是否有意义)
5.2 赋值运算符重载
6.const成员
7.取地址以及const取地址操作符重载
8.日期类的实现
Date.h
Date.cpp
9 流插入和流提取运算符重载
9.1自定义类型流插入的实现
9.2 自定义类型流提取的实现
本次大纲内容:
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Date {};
2..构造函数
2.1概念
对于以下Date类:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 7, 5);
d1.Print();
Date d2;
d2.Init(2022, 7, 6);
d2.Print();
return 0;
}
对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2 特征
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值(也不需要写void)。
3. 对象实例化时编译器自动调用对应的构造函数。
struct Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack:;malloc");
return;
}
_top = 0;
_capacity = capacity;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
return 0;
}
测试运行:
可以看到,确实是自动调用了
注:
4. 构造函数可以重载。
class Date
{
public:
Date()
{}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
return 0;
}
可以看到,构造函数可以构成重载
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
可以看到函数还是随机值
这是因为C++规定编译器生成的默认构造函数,只会初始化构造类型,而不会初始化内置类型
6.关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char/double...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。
我们不写,编译器默认生成构造函数,内置类型不做处理,自定义类型会去调用它的默认构造函数
有些编译器也会处理内置函数,但是那是个性化处理,不是所有编译器都会处理
注:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
class Date
{
public:
void Print()
{
cout << _year <<' ' << _month << ' ' << _day << endl;
}
private:
// 基本类型(内置类型)
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
Date d;
d.Print();
return 0;
}
tips:
在类中给声明赋初始值,不是给它们初始化的意思,是给默认构造函数的形参赋予缺省值
7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。
所以这两个只能留一个
结论:
- 一般情况下,有内置类型成员的,就需要自己些构造函数,不能使用编译器生成的
- 全部都是自定义类型可以考虑让编译器自己生成
3.析构函数
3.1 概念
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 特性
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
struct Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack:;malloc");
return;
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
return 0;
}
运行测试:
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(内置类型不做处理,自定义类型会去调用它的析构函数)。
注:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
结论:
- 一般情况下,有动态申请资源,就需要显示写析构函数释放资源
- 没有动态申请资源,不需要写析构函数
- 需要释放资源的成员都是自定义类型,不需要写析构
4.拷贝构造函数
4.1 概念
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
class Date
{
public:
Date(const Date& d)
{
cout << "const Date& d" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
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;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
这里解释一下为什么拷贝构造函数中需要使用const Date& d这种形式来接收形参
为什么不能使用Date d呢?
- 内置类型,不会去调用拷贝构造函数拷贝
- 自定义类型,必须借用拷贝构造函数来完成拷贝
看图:
因为我们传的是自定义类型,当我们将d1传到函数Date的途中,d1就会被调用到新的拷贝构造函数中,重复前面的步骤,不断递归,最后肯定会栈溢出,但是编译器不允许这样传参,会直接报错
解决方法:
- 传指针:任何类型的指针都是内置类型,拷贝时不会调用拷贝构造函数
- 传引用:直接使用d1的别名
这里建议使用引用,并且加个const(类中不受访问限定符的影响)
3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
- 编译器自动生成的默认拷贝构造函数,会对内置类型完成值拷贝/浅拷贝
- 自定义类型,调用它的拷贝构造函数
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;
};
int main()
{
Date d1(2024, 9, 7);
Date d2(d1);
return 0;
}
对于这种日期类还是可以使用默认构造函数的
但是有另一种情况:
因为默认拷贝构造函数是浅拷贝,遇到动态申请空间的情况就会出现问题
struct Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack:;malloc");
return;
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
这里的问题:
(1)析构函数对同一个空间清理了两次,会报错
(2)改变一个空间的内容会对另外一个空间造成影响
如图:
指向同一块空间
这里使用浅拷贝肯定不行
这里栈得我们手动写一个深拷贝,后期我们会学到深拷贝来解决这里的问题
这里给大家浅浅讲一下深拷贝
深拷贝就是为了弥补浅拷贝的缺点而存在的
如图:
代码实现:
struct Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack:;malloc");
return;
}
_top = 0;
_capacity = capacity;
}
//深拷贝需要自己手动实现
Stack(const Stack& st1)
{
_a = (int*)malloc(sizeof(int) * st1._capacity);
if (nullptr == _a)
{
perror("Stack::malloc");
return;
}
memcpy(_a, st1._a, sizeof(int) * st1._top);
_capacity = st1._capacity;
_top = st1._top;
}
~Stack()
{
cout << "~Stack" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
这里看几个情况:
(1).
1. void func1(Date& d);
2. void func2(Date d);
- 如果没有动态资源的类类型,这上面两种形式肯定是第一个好,因为使用引用为形参在语法上是直接使用d的空间,不会去额外开辟栈帧,这样会减少消耗,加快程序运行效率
(2).
1.
Stack& func1()
{
static Stack st;
return st;
}
2.
Stack func1()
{
Stack st;
return st;
}
3.
Stcak& func1()
{
Stack st;
return st;
}
int main()
{
Stack ret = func1();
return 0;
}
(1).st出栈帧后不会被销毁,因为st存放在静态区,故而可以使用引用作为返回值
(2)(3).在2,3中只能使用3这种形式
- 解释(2):调用func1函数,在func1函数中创建一个变量st,然后返回st变量,返回时编译器会产生一个临时变量来保存st的内容,这时就需要调用拷贝构造函数,将st的内容拷贝给这个临时变量,通过这个临时变量将st的内容给ret,因为是深拷贝,所以会增加空间的使用
- 解释(3):因为st是临时变量出栈帧结束就销毁了,销毁之后st会调用析构函数来清理它的资源,因为是引用,所以不会产生临时变量,这时ret和st指向的内容相同,即编译器报错
5.赋值运算符重载函数
5.1 运算符重载(是否重载这个运算符是看这个运算符对这个类是否有意义)
C++为什么要加入运算符重载函数呢?
1. C++提供的一些运算符只能供 内置类型/基本类型使用,自定义类型使用不了,因为内置类型原本就是C++本来就有的,创造C++的大佬知道怎么实现它们的运算符,而自定义类型是我们自己定义的所以,需要我们自己实现,运算符重载就是 让自定义类型支持运算符
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
d1 == d2; //书写简单,可读性高
assignment(d1, d2); //书写复杂,可读性低
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数(自定义类型)
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
下面我通过创建日期类来了解运算符重载函数
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 9, 10);
Date d2(2024, 9, 10);
//operator==(d1, d2);与下面那个效果一样
int ret = d1 == d2;//自定义类型使用运算符
cout << ret << endl;
return 0;
}
这里我使用的是全局的运算符重载函数,但是有一定的缺陷,保证不了类的封装性,类中的对象全部公开了
注:operator==(d1, d2);和d1 == d2是一样的
我们看看底层实现(汇编代码):
这里有两个办法
1.将运算符重载函数写成日期类成员函数,写到类中不会受访问限定符的影响
2.在日期类中声明operator==为友元函数
第二个办法我们后面在讲,这里说明第一个方法
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 9, 10);
Date d2(2024, 9, 10);
//d1.operator==(d2);与下面那个效果一样
int ret = d1 == d2;、
cout << ret << endl;
return 0;
}
注:写成成员函数要注意operator==的有操作数是this指针,所以operator==函数参数只有一个,但实际上有两个,只是this指针不能显示在参数表和函数调用中(形参和实参)
5.2 赋值运算符重载
1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 9, 10);
Date d2;
d2 = d1;
d2.Print();
return 0;
}
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
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;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& d1, const Date& d2)
{
if (&d1 != &d2)
{
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2._day;
}
return d1;
}
int main()
{
Date d1(2024, 9, 10);
Date d2;
d2 = d1;
d2.Print();
return 0;
}
编译失败:error C2801: “operator =”必须是非静态成员
原因:赋值运算符如果不显式实现(注:只是赋值运算符),编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注 意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。
默认生成的赋值重载函数的作用和拷贝构造一样:
- 内置类型进行值拷贝/深拷贝
- 自定义类型调用它的赋值重载函数
class Time //我这里将std全展开了,如果写time会和库中的time冲突
{
public:
Time(int hour = 1, int minute = 1, int second = 1)
{
_hour = hour;
_minute = minute;
_second = second;
}
Time& operator=(const Time& T)
{
if (this != &T)
{
_hour = T._hour;
_minute = T._minute;
_second = T._second;
}
return *this;
}
//private:
int _hour;
int _minute;
int _second;
};
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;
cout << _T._hour << ' ' << _T._minute << ' ' << _T._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _T;
};
int main()
{
Date d1(2024, 9, 10);
Date d2;
d2 = d1;
d2.Print();
return 0;
}
总结:
1.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
2.赋值前检查是否在给自己赋值
C++支持d1 = d1这样赋值的情况,这种赋值是没有意义的,这种操作只会增加空间复杂度,所以我们可以在赋值函数中增加一个判断语句,如果是d1 = d1这种情况,返回d1就可以了
3.函数的返回值使用引用返回
实际上若是出现d2 = d1;这种情况我们没有必要有返回值,因为在函数内已经通过this指针改变了对象d2,但是为了支持连续赋值d3 = d2 = d1 ,我们就需要为函数设置一共返回值,显而易见的我们应该返回运算符的左操作数,即*this
如果返回的*this出作用域不会被销毁,那么为了避免不必要的消耗,最好使用引用返回
4.参数类型设置为引用,并使用const修饰
赋值运算符重载函数第一个参数默认是this指针,第二个参数是运算符的右操作数
由于是自定义类型,我们若使用传值传参,这里系统就会自动调用一次拷贝构造函数,所以我们这里使用引用传递(第一个参数this是不显示的,我们管不了)
第二个操作数原本就是给第一个操作数赋值的,所以这里我们还能在它前面加上一个const防止它的数据被篡改
5.拷贝构造函数和赋值运算符
拷贝构造函数:用一个已经存在的对象初始化另一个对象
赋值运算符重载:两个存在对象之间的赋值拷贝
例:
int main()
{
Date d1(2024, 9, 11);
Date d2(d1); //或 d2 = d1; 拷贝构造函数
Date d3(2024, 9, 12);
d3 = d1; // 或d3.operator(d1); 赋值运算符重载函数
return 0;
}
6.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public:
void Print() const
{
cout << _year << "年" << _month
<< "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
}
实际编译器的处理,这段代码只是给予理解的,实际上this指针是不能显现的
这里其实是const Date* const this,但是为了好理解,写成 const Date* this
class Date
{
public:
void Print(const Date *this)
{
cout << _year << "年" << _month
<< "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
}
请思考下面的几个问题:
1. const对象可以调用非const成员函数吗? 不可以
2. 非const对象可以调用const成员函数吗? 可以
3. const成员函数内可以调用其它的非const成员函数吗? 不可以
4. 非const成员函数内可以调用其它的const成员函数吗? 可以
解释:
1. 我们将const对象传进非const函数中,函数中的this指针没有被const修饰,而我们传进来的形参是被const所修饰的不能随意篡改内容,但是一旦传进函数中,形参的权限就被放大了,不受const的限制了
2.将非const对象传进const成员函数中,属于权限的缩小,这样是可行的
3.在const成员函数内调用非const成员函数,是将被const修饰的指针this的值赋值给另一个非const成员函数,这属于权限的放大,不可行
4.在非const成员函数内调用const成员函数,是将指针this的值赋值给另一个const成员函数,这属于权限的缩小,是可行的
7.取地址以及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容!
特殊使用场景,让别人取不到对象的地址:
class Date
{
public:
Date* operator&()
{
//return this;
return nullptr;
}
const Date* operator&()const
{
return this;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
8.日期类的实现
Date.h
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
//获取这个月的天数
int GetMonthDay(int _year, int _month);
//日期 += 天数
Date& operator+=(int day);
//日期 -= 天数
Date& operator-=(int day);
//日期 + 天数
Date operator+(int day) const;
//日期 - 天数
Date operator-(int day) const;
//日期 == 日期
bool operator==(const Date& d) const;
//日期 >= 日期
bool operator>=(const Date& d) const;
//日期 > 日期
bool operator>(const Date& d) const;
//日期 <= 日期
bool operator<=(const Date& d) const;
//日期 < 日期
bool operator<(const Date& d) const;
//日期 != 日期
bool operator!=(const Date& d) const;
//日期 - 日期
Date operator-(const Date& d);
//前置++
Date& operator++();
//后置++
Date operator++(int);
void Print();
private:
int _year;
int _month;
int _day;
};
Date.cpp
Date::Date(int year, int month, int day)
{
if (month > 0 && month < 13
&& day > 0 && day <= GetMonthDay(month, day))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "无效天数,请重新输入" << endl;
assert(false);
}
}
//获取这个月的天数
int Date::GetMonthDay(int year, int month)
{
int MonthDay[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 MonthDay[month];
}
}
//日期 - 天数
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year,_month))
{
if (_month == 13)
{
++_year;
_month = 1;
}
_day -= GetMonthDay(_year, _month);
++_month;
}
return *this;
}
//日期 -= 天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 0)
{
_day += GetMonthDay(_year, _month);
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
}
return *this;
}
//日期 + 天数
Date Date::operator+(int day) const
{
//Date tmp = *this;
//tmp += 100;
//return tmp;
Date tmp = *this;
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-(int day) const
{
//Date tmp = *this;
//tmp -= 100;
//return tmp;
Date tmp = *this;
tmp._day -= day;
while (tmp._day < 0)
{
tmp._day += GetMonthDay(tmp._year, tmp._month);
--tmp._month;
if (tmp._month == 0)
{
--tmp._year;
tmp._month = 12;
}
}
return tmp;
}
//日期 == 日期
bool Date::operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//日期 != 日期
bool Date::operator!=(const Date& d) const
{
return !(*this == d);
}
//日期 >= 日期
bool Date::operator>=(const Date& d) const
{
if (_year >= d._year)
{
return true;
}
else if (_year == d._year && _month >= d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day >= d._day)
{
return true;
}
return false;
}
//日期 <= 日期
bool Date::operator<=(const Date& d) const
{
return *this < d || *this == d;
}
//日期 < 日期
bool Date::operator<(const Date& d) const
{
return !(*this >= d);
}
//日期 > 日期
bool Date::operator>(const Date& d) const
{
return !(*this <= d);
}
//前置++,没有创建新的变量
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++
//创建了两个新对象,因为需要构成重载,所以在后置++上的参数表中需要带上int
//一般前置++用的多一些,所以在后置上加int
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
//日期 - 日期
Date 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 n * flag;
}
void Date::Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
9 流插入和流提取运算符重载
我们知道cin和cout是在iostream库中的
通过这张图可知cin是定义istream中,而cout定义在osteam中
看图:
这张图解释了
cout会自动识别内置类型,是因为它会去调用与之对应的运算符重载函数。
cout可以直接支持内置类型,是因为它已经在库中实现了
9.1自定义类型流插入的实现
class Date
{
public:
void operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 9, 10);
//d1.operator<<(cout);
d1 << cout;
return 0;
}
但是这样看起来是不是很奇怪我们能不能将d1<<cout 改为 cout<<d1;
将operator<<函数改为全局函数
class Date
{
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out,const Date& d)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
int main()
{
Date d1(2024, 9, 10);
//d1.operator<<(cout);
d1 << cout;
return 0;
}
这里写成全局函数有两个要注意的点
1. 因为日期类中的成员变量是私有的,不能在类外使用
2. 写成全局函数,参数需要都显示出来
这里有两个解决方法:
(1). 我们可以将类成员变量写成函数
例:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int getyear()
{
return _year;
}
int getmonth()
{
return _month;
}
int getday()
{
return _day;
}
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out,const Date& d)
{
out << d.getyear() << "年" << d.getmonth() << "月" << d.getday() << "日" << endl;
}
int main()
{
Date d1(2024, 9, 10);
//cout.operator<<(d1);
cout << d1;
return 0;
}
(2).在类中声明友元函数
class Date
{
//声明友元函数
friend void operator<<(ostream& out, const Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out,const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
int main()
{
Date d1(2024, 9, 10);
//cout.operator<<(d1);
cout << d1;
return 0;
}
声明成友元函数就可以使用类中的资源了
cout连续打印:cout << d1 << d2 << d3; ,这时重载函数就需要一个返回值来完成这一操作
cout << d1 << d2 << d3;的顺序是 d1先输出,然后d2,d3。它们的返回值是cout 即:ostream
连续赋值的函数实现:
class Date
{
//声明友元函数,在类中哪定义都可以
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out,const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
int main()
{
Date d1(2024, 9, 10);
Date d2, d3;
cout << d1 << d2 << d3;
return 0;
}
9.2 自定义类型流提取的实现
class Date
{
//声明友元函数,在类中哪定义都可以
friend istream operator>>(istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
istream& operato>>(istream& in, Date& d)
{
int year, month, day;
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;
}
return in;
}
int main()
{
Date d1(2024, 9, 10);
Date d2, d3;
cin >> d1 >> d2 >> d3;
cout << d1 < d2 << d3;
return 0;
}
istream& operato>>(istream& in, Date& d);这条语句中的参数都不可以加const
in是向对象d中写数据,d不能加const
写数据的过程中会改变in中的一些状态值,in不能加const