目录
构造函数补充
构造函数初始化列表的使用
赋值运算符重载函数
运算符重载函数介绍
运算符重载函数的使用
赋值运算符重载函数
赋值运算符重载函数的使用
拷贝构造函数和赋值运算符重载函数
重载前置++和后置++
前置++
后置++
重载流插入<<与流提取>>
流插入运算符<<重载
流提取运算符>>重载
const成员函数
取地址操作符重载与const成员取地址操作符重载
实现日期类练习
声明:本篇为C++默认构造函数最后一篇
构造函数补充
在C++中,可以在构造函数的函数体中为变量进行初始化,但是实际上该过程并不是初始化,可以理解为赋值,因为对象还没有真正创建,并且初始化只能初始化一次,但是赋值可以执行多次,而当对象创建时,所有成员变量就是当成整体创建,那么每一个变量在和何处被初始化变成了一个问题,为了解决这个问题,C++标准中引入构造函数初始化列表,只要是本类的成员变量都是在初始化列表处初始化,具体初始化的内容由程序员自己决定
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 23)
:_year(year)
, _month(month)
, _day(day)
{}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d;
d.print();
return 0;
}
输出结果:
2024/3/23
构造函数初始化列表的使用
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式,语法如下
类名(参数列表)
:成员变量(值)
,成员变量(值)
,成员变量(值)
{}
📌
注意,并不是所有成员变量都需要写进初始化列表,没写入初始化列表的成员变量也会像写入初始化列表中的成员变量一样走一遍初始化列表,只是没有显式
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace std;
class Array
{
private:
int* _arr;
int _size;
int& ref;
public:
Array()
:_arr(nullptr)
, _size(4)
, ref(_size)//引用类型必须初始化
{
_arr = (int*)malloc(sizeof(int) * _size);
assert(_arr);
}
Array(const Array& data)
:ref(_size)//拷贝构造中,引用类型也必须初始化
{
_arr = (int*)malloc(sizeof(int) * data._size);
assert(_arr);
for (int i = 0; i < data._size; i++)
{
_arr[i] = data[i];
}
}
//重载[]
int& operator[](int i)
{
return _arr[i];
}
//const类型的引用,不可以通过返回的引用改变数组中的值
const int& operator[](int i) const
{
return _arr[i];
}
};
int main()
{
Array a;
for (int i = 0; i < 4; i++)
{
a[i] = i + 1;
}
const Array p(a);
for (int i = 0; i < 4; i++)
{
cout << p[i] << " ";
}
return 0;
}
输出结果:
1 2 3 4
const
成员变量- 自定义类型成员(且该类没有默认构造函数时)
#include <iostream>
using namespace std;
class Time
{
private:
int _time;
public:
Time(int time)
{
}
};
class Date
{
private:
int _year;
int _month;
int _day;
Time _t;
public:
Date()
:_year(2023)
,_month(3)
,_day(21)
{}
};
int main()
{
Date d;
return 0;
}
报错信息:
类 "Time" 不存在默认构造函数
在C++11标准规范中,可以在成员变量创建的同时给缺省值,此时如果给了这个缺省值,想使用缺省值时就不需要再将该成员变量写入初始化列表中,如果不想使用缺省值,则将成员变量写入初始化列表中并给定初始值,否则默认初始值为0
#include <iostream>
using namespace std;
//构造函数初始化列表
class Date
{
private:
int _year = 2023;
int _month = 2;
int _day = 28;
public:
Date()
//_year没有写入初始化列表,使用缺省值
: _month(3)//写入初始化列表中,给了初始值为3,使用初始值
, _day()//写入初始化列表中,但是没给初始值,默认初始值为0
{}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d;
d.print();
return 0;
}
输出结果:
2023/3/0
在初始化列表中无法处理例如动态申请内存的行为,此时可以在函数体内完成,例如
#include <iostream>
#include <cstdlib>
#include <cassert>
using namespace std;
class Array
{
private:
int* _arr;
int _size;
public:
Array()
:_arr(nullptr)
,_size(4)
{
_arr = (int*)malloc(sizeof(int) * _size);//在构造函数体中分配空间
assert(_arr);
}
Array(const Array& data)
{
_arr = (int*)malloc(sizeof(int) * data._size);
assert(_arr);
for (int i = 0; i < data._size; i++)
{
_arr[i] = data[i];
}
}
//重载[]
int& operator[](int i)
{
return _arr[i];
}
const int& operator[](int i) const
{
return _arr[i];
}
};
int main()
{
Array a;
for (int i = 0; i < 4; i++)
{
a[i] = i + 1;
}
const Array& p(a);
for (int i = 0; i < 4; i++)
{
cout << p[i] << " ";
}
return 0;
}
输出结果:
1 2 3 4
所以,如果不使用缺省值,尽量使用初始化列表初始化,因为不管是否使用初始化列表,对于自定义类型的成员变量,一定会先使用初始化列表初始化
📌
注意:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
输出结果:
1 -858993460
在上面的代码中,因为成员变量_a2
比_a1
先声明,所以在初始化时先走_a2(_a1)
,所以_a2
被初始化为随机值,接着再初始化_a1
,所以_a1
为1
赋值运算符重载函数
运算符重载函数介绍
在C++中,为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//自定义类型无法使用内置的关系运算符进行比较
return 0;
}
报错信息:
二进制“==”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换
为了自定义类型的对象之间可以进行关系运算,可以使用运算符重载,如下面代码
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int getYear()
{
return _year;
}
int getMonth()
{
return _month;
}
int getDay()
{
return _day;
}
};
bool operator==(Date& d1, Date& d2)
{
return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//有重载==的函数时可以比较
return 0;
}
输出结果:
1
运算符重载函数的使用
对于运算符重载函数来说,其函数名为:operator+需要重载的运算符
,而该函数的原型如下:
函数返回类型 operator运算符(参数列表)
定义运算符重载函数时,需要注意下面的问题
- 不能通过连接其他符号来创建新的操作符:比如
operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型
+
,不 能改变其含义 - 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的
this
.* :: sizeof ?: .
:注意以上5个运算符不能重载
对于下面的代码
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int getYear()
{
return _year;
}
int getMonth()
{
return _month;
}
int getDay()
{
return _day;
}
};
//全局运算符重载函数
bool operator==(Date& d1, Date& d2)
{
return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//有重载==的函数时可以比较
return 0;
}
输出结果:
1
在上面的代码中,因为运算符重载函数不在类中,并且因为类的成员变量为private
,所以需要调用获取函数来得到当前对象的成员变量中的值,并且因为在全局中,并不存在哪一个对象调用函数,所以没有this
指针,此时形参的个数对应运算符的操作数的个数
📌
注意上面的全局运算符重载函数中形参不可以使用const
修饰,因为如果使用了const
修饰,那么就是d1
和d2
都是const
修饰的对象,而this
只是*const
,而不是const*
,本来是d1
和d2
被const
修饰不可以修改引用的对象的值,但是如果传递给了this
可能会出现通过this
改变d1
和d2
引用的对象的值,所以此处涉及到将引用的权限放大
考虑到如果将运算符重载函数写在类外需要额外写三个函数来获取到指定的值,所以可以将运算符重载函数写进类中
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//类中的运算符重载函数
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
};
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//直接调用类中实现的运算符重载函数
//上面的代码相当于:cout << d1.operator==(d2) << endl;
return 0;
}
输出结果:
1
在上面的代码中,因为运算符重载函数在类中,所以存在this指针,所以只需要传递一个参数(加上this
指针参数和额外的参数一共两个参数对应==
操作符的操作数个数),并且形参对象引用d指的是第二个操作数,因为d1 == d2
等价于d1.operator==(d2)
,因为是d1
在调用运算符重载函数,所以this
指针指向的对象即为d1
赋值运算符重载函数
赋值运算符重载函数也是运算符重载函数中的一种,因为重载的运算符为赋值运算符=
,重载赋值运算符时,首先不能改变赋值运算符的特性,包括连续赋值
赋值运算符重载函数的使用
赋值运算符重载函数的格式
- 参数类型:
const T&
,传递引用可以提高传参效率(T
为类名) - 返回值类型:
T&
,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,并且需要检测是否自己给自己赋值,以减少赋值次数 - 返回
*this
:要复合连续赋值的含义
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
};
int main()
{
Date d(2024, 2, 28);
Date d1;
d1 = d;
d1.print();
return 0;
}
输出结果:
2024/2/28
在上面的代码中,类Date
中对赋值运算符进行了重载,将引用指向的对象中的值给调用该运算符重载函数的对象,但是上面的代码无法实现赋值运算符的连续赋值,因为没有返回值
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
};
int main()
{
Date d(2024, 2, 28);
Date d1;
Date d2;
d1 = d;
d1.print();
d2 = d1 = d;
return 0;
}
报错信息:
二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)
所以,为了解决这个问题,赋值运算符重载函数需要给定返回值为类类型
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
};
int main()
{
Date d(2024, 2, 28);
Date d1;
Date d2;
d2 = d1 = d;
d1.print();
d2.print();
return 0;
}
输出结果:
2024/2/28
2024/2/28
在上面的代码中,赋值运算符重载函数给了返回值为Date
类型的引用,此时可以使用连续赋值,因为赋值运算符从右往左结合,所以具体过程为d对象赋值给d1,d1对象的值赋值给d2,从函数调用的角度理解为d2.operator=(d1.operator=(d));
(注意不是d2.operator=(d1).operator=(d);
,本句理解为d2
被赋值为d1
中的内容,然后再被赋值为d
中的内容,相当于d2 = d1; d2 = d;
)
📌
赋值运算符重载函数的返回值也可以不用引用,但是此时在返回时会调用拷贝构造函数将返回值的内容拷贝给调用赋值运算符重载函数的对象,为了减少调用拷贝构造的次数,更推荐使用引用,该解释同样适用于形参
另外,还有一个小问题,如果两个相同的对象进行赋值,那么将产生额外的一次赋值,对于这个问题,在赋值时需要判断形参引用的对象和this
指针指向的对象是否是同一个地址的对象
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
int main()
{
Date d(2024, 2, 28);
d = d;
return 0;
}
在上面的代码中,判断this
指针指向的对象的地址和引用的对象地址是否相等,如果二者相等,则证明是同一个对象,不需要进行赋值直接返回即可,注意形参的Date &d
为创建对象的引用,而if
语句中的&d
是取引用的地址
注意,赋值运算符重载函数必须作为成员函数,不能作为全局函数
#include <iostream>
using namespace std;
class Date
{
public:
int _year;
int _month;
int _day;
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
Date& operator=(Date& d1, Date& d)
{
if (&d1 != &d)
{
d1._year = d._year;
d1._month = d._month;
d1._day = d._day;
}
return d1;
}
int main()
{
Date d(2024, 2, 28);
Date d1;
d1 = d;
return 0;
}
报错信息:
“operator=”必须是成员函数
赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,所以赋值运算符重载只能是类的成员函数
拷贝构造函数和赋值运算符重载函数
与拷贝构造函数类似,赋值运算符重载函数如果用户没有实现,编译器会自动实现。默认如果不自主实现还是按照字节拷贝,按照字节方式拷贝也会遇到像拷贝函数一样的问题(指对象中有资源申请时)
- 一个对象改变,另一个对象也会跟着改变,严重者会数据覆盖
- 释放资源时会因为多次释放一个空间导致程序崩溃
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d(2024, 2, 28);
Date d1;
d1 = d;//编译器自动生成的默认赋值运算符重载函数
Date d2(d);//编译器自动生成的拷贝构造函数
d1.print();
d2.print();
return 0;
}
输出结果:
2024/2/28
2024/2/28
与拷贝构造函数一样,如果类对象中有涉及到资源申请,那么需要自己实现赋值运算符重载函数,否则直接使用默认的即可
重载前置++和后置++
前置++
对于运算符重载函数的使用规则,那么可以很容易写出++
的重载函数,如下:
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void operator++()
{
_day += 1;
}
};
int main()
{
Date d;
++d;
d.print();
return 0;
}
输出结果:
2024/3/23
因为前++
相当于计算+=1
,而因为前面实现过获取X
天后的日期的函数GetAfterXDays_plusEqual
,所以可以直接用该函数进行复用,从而实现++
操作
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
else
{
return monthDays[month];
}
}
Date& GetAfterXDays_plusEqual(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date GetAfterXDays_plus(int days)
{
Date tmp(*this);
tmp._day += days;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
void operator++()
{
//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
*this = GetAfterXDays_plusEqual(1);
}
};
int main()
{
Date d;
++d;
d.print();
return 0;
}
输出结果:
2024/3/23
注意到上面实现的++
是无返回值的++
运算符重载函数,但是如果函数没有返回值,将无法将++
后的值给另外一个对象
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
else
{
return monthDays[month];
}
}
Date& GetAfterXDays_plusEqual(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date GetAfterXDays_plus(int days)
{
Date tmp(*this);
tmp._day += days;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
void operator++()
{
//_day += 1;
//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
*this = GetAfterXDays_plusEqual(1);
//return *this;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
int main()
{
Date d;
++d;
d.print();
Date d1;
d1 = ++d;
return 0;
}
报错信息:
二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)
所以为了解决这种问题将考虑为++
运算符重载函数加上返回值为类对象引用类型
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
else
{
return monthDays[month];
}
}
Date& GetAfterXDays_plusEqual(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date GetAfterXDays_plus(int days)
{
Date tmp(*this);
tmp._day += days;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
Date& operator++()
{
//_day += 1;
//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
*this = GetAfterXDays_plusEqual(1);
return *this;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
int main()
{
Date d;
++d;
d.print();
Date d1;
d1 = ++d;
return 0;
}
输出结果:
2024/3/23
后置++
上面的函数中实现了前置++,但是并没有实现后置++,如果在没有实现后置++时,使用后置++,则会出现下面的情况
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
else
{
return monthDays[month];
}
}
Date& GetAfterXDays_plusEqual(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date GetAfterXDays_plus(int days)
{
Date tmp(*this);
tmp._day += days;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
Date& operator++()
{
//_day += 1;
//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
*this = GetAfterXDays_plusEqual(1);
return *this;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
int main()
{
Date d;
d++;
return 0;
}
报错信息:
二进制“++”:“Date”不定义该运算符或到预定义运算符可接收的类型的转换
所以有前置++的实现并不能同时应用于后置++,对于后置++来说,编译器为了与前置++作区分,需要在形参位置添加一个整型占位形参,如下
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
else
{
return monthDays[month];
}
}
Date& GetAfterXDays_plusEqual(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date GetAfterXDays_plus(int days)
{
Date tmp(*this);
tmp._day += days;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
//前置++
Date& operator++()
{
//_day += 1;
//因为++相当于+=1,所以可以直接复用GetAfterXDays_plusEqual(1)
*this = GetAfterXDays_plusEqual(1);
return *this;
}
//后置++,但是为了不同于前置++,在形参处加入int形参作为占位便于编译器区分
Date operator++(int)
{
//后置++满足先使用再++,所以返回值为原值
Date tmp(*this);
*this = GetAfterXDays_plusEqual(1);
return tmp;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
};
int main()
{
Date d;
d++;
d.print();
return 0;
}
输出结果:
2024/3/23
在上面的代码中,对于后置++
重载函数来说,在形参处添加了一个int
类型形参作为占位符,这个形参可以不给形参名,因为只是编译器用于区分
重载流插入<<与流提取>>
在C++标准库中,cout
和cin
是属于iostream
中ostream
和istream
的对象,对于流插入<<
运算符,之所以cout
输出可以不用指定占位符编译器可以自动匹配的原因是ostream
中<<
的运算符重载和函数重载,对于内置类型来说,有下面的函数重载
同样对于流提取运算符>>
来说也是如此,如下图所示
但是流插入和流提取运算符并没有对自定义类型有函数重载,所以可以对流提取运算符和流插入运算符进行函数重载
流插入运算符<<重载
按照前面的运算符重载思路可以写出下面的代码
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void operator<<(ostream& cout)
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d;
cout << d;
return 0;
}
报错信息:
二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
在上面的代码中,虽然重载了<<
,但是形参是ostream
流的对象,而隐含的形参是this
,而在运算符重载函数形参列表的规则中,对于有两个操作数的运算符重载来说,第一个参数为左操作数,第二个参数为右操作数,所以上面的代码调用应该为d << cout
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
void operator<<(ostream& cout)
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d;
//cout << d;
d << cout;
return 0;
}
输出结果:
2024/3/22
但是和正常的输出刚好顺序相反,所以这种方法需要改变,但是因为不能改变this
在形参的位置,所以考虑到将<<
重载放置到全局中,此时可以决定两个操作数的顺序,但是这个方法就不能使用this指针,并且需要考虑到成员变量的private
属性,本处给出一种解决方案是使用get
函数
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
int getYear()
{
return _year;
}
int getMonth()
{
return _month;
}
int getDay()
{
return _day;
}
};
void operator<<(ostream& cout, Date& d)
{
cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
}
int main()
{
Date d;
cout << d;
return 0;
}
输出结果:
2024/3/22
在上面的代码中,将流插入运算符重载函数放置到全局中可以控制cout
和d
的顺序,此时即可写为cout << d
,但是因为上面的<<
并没有返回值,所以不可以连续插入,所以可以改进为如下
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
int getYear()
{
return _year;
}
int getMonth()
{
return _month;
}
int getDay()
{
return _day;
}
};
ostream& operator<<(ostream& cout, Date& d)
{
cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
return cout;
}
int main()
{
Date d;
Date d1;
cout << d << d1 << endl;
return 0;
}
输出结果:
2024/3/22
2024/3/22
流提取运算符>>重载
对于流提取运算符>>的重载类似于流插入运算符<<,但是此时不能使用获取函数,所以对于流提取运算符的重载来说,考虑用友元解决,使用友元可以让全局函数中的对象获取到private
属性的变量
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
//友元
friend istream& operator>>(istream& cin, Date& d);
int getYear()
{
return _year;
}
int getMonth()
{
return _month;
}
int getDay()
{
return _day;
}
};
ostream& operator<<(ostream& cout, Date& d)
{
cout << d.getYear() << "/" << d.getMonth() << "/" << d.getDay() << endl;
return cout;
}
istream& operator>>(istream& cin, Date& d)
{
cin >> d._year >> d._month >> d._day;
return cin;
}
int main()
{
Date d;
Date d1;
cin >> d >> d1;
cout << d << d1 << endl;
return 0;
}
输入:
2024 2 28 2024 3 31
输出结果:
2024/2/28
2024/3/31
使用友元解决流插入运算符的重载函数
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
Date(int year = 2024, int month = 3, int day = 22)
{
_year = year;
_month = month;
_day = day;
}
//友元
friend istream& operator>>(istream& cin, Date& d);
friend ostream& operator<<(ostream& cout, Date& d);
};
ostream& operator<<(ostream& cout, Date& d)
{
cout << d._year << "/" << d._month << "/" << d._day << endl;
return cout;
}
istream& operator>>(istream& cin, Date& d)
{
cin >> d._year >> d._month >> d._day;
return cin;
}
int main()
{
Date d;
Date d1;
cin >> d >> d1;
cout << d << d1 << endl;
return 0;
}
输入:
2024 2 23 2022 3 31
输出结果:
2024/2/23
2022/3/31
const成员函数
将const
修饰的“成员函数”称之为const
成员函数,const
修饰类成员函数,实际修饰该成员函数隐含的this
指针,表明在该成员函数中不能对类的任何成员进行修改
在前面的运算符重载函数中,当运算符重载函数在全局时不可以使用const修饰形式参数,因为const成员变量传递给成员函数时涉及到引用权限放大,那么const成员函数就是用来解决这种权限放大问题,可以将权限保持不变或者缩小,例如在下面的代码中
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int getYear() const
{
return _year;
}
int getMonth() const
{
return _month;
}
int getDay() const
{
return _day;
}
};
//全局运算符重载函数,const形参
bool operator==(const Date& d1, const Date& d2)
{
return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//有重载==的函数时可以比较
return 0;
}
输出结果:
1
在上面的代码中,使用const
修饰了==
运算符重载函数的两个形式参数,此时d1
和d2
不可以被修改,当对象d1
和d2
调用get
系列函数时,成员函数的形式参数需要保证获得的权限不被放大,所以需要修饰形式参数,但是因为this
指针不可以直接显式做形式参数,所以不可以使用const
显式对this
指针进行修饰,此时就需要将const
放置到函数名后,作为修饰this
指针的const
以满足指针及引用权限不被放大
但是,如果==
运算符重载函数中的两个形式参数并不是const
修饰的变量,此时调用const
成员函数也不会有错,因为此时是权限的缩小(从可修改到成员函数的不可修改),如下面的代码
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
int getYear() const
{
return _year;
}
int getMonth() const
{
return _month;
}
int getDay() const
{
return _day;
}
};
//全局运算符重载函数,非const形参
bool operator==(Date& d1, Date& d2)
{
return d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth() && d1.getDay() == d1.getDay();
}
int main()
{
Date d1;
Date d2(d1);
cout << (d1 == d2) << endl;//有重载==的函数时可以比较
return 0;
}
对于const
成员函数和非const
成员函数之间也是如此
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//非const成员函数
void printYear()
{
cout << _year;
}
int getYear() const
{
printYear();
return _year;
}
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
报错信息:
“void Date::printYear(void)”: 不能将“this”指针从“const Date”转换为“Date &”
而对于const
成员函数和const
成员函数之间可以直接调用
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//非const成员函数
void printYear() const
{
cout << _year;
}
int getYear() const
{
printYear();
return _year;
}
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
同样非const
成员函数可以调用const
成员函数
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//非const成员函数
void printYear() const
{
cout << _year;
}
int getYear()
{
printYear();
return _year;
}
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
取地址操作符重载与const成员取地址操作符重载
在C++中,这两个运算符重载函数可以不用显式定义,编译器会默认生成
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1;
Date d2;
const Date d3;
cout << &d1 << endl;
cout << &d2 << endl;
cout << &d3 << endl;
return 0;
}
输出结果:
00000031E9FEF628
00000031E9FEF658
00000031E9FEF688
也可以显式定义
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
};
int main()
{
Date d1;
Date d2;
const Date d3;
cout << &d1 << endl;
cout << &d2 << endl;
cout << &d3 << endl;
return 0;
}
输出结果:
0000006B086FFC38
0000006B086FFC68
0000006B086FFC98
只有特殊情况,才需要重载这两个函数,比如想让别人获取到指定的内容,让其访问非法地址
#include <iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year = 2024, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return (Date*)0;
}
const Date* operator&()const
{
return (Date*)0;
}
};
int main()
{
Date d1;
Date d2;
const Date d3;
cout << &d1 << endl;
cout << &d2 << endl;
cout << &d3 << endl;
return 0;
}
输出结果:
0000000000000000
0000000000000000
0000000000000000
实现日期类练习
//头文件
#pragma once
#include <iostream>
using namespace std;
class Date
{
private:
int _day;
int _month;
int _year;
public:
//构造函数
Date(int year = 2024, int month = 3, int day = 23)
:_year(year)
,_month(month)
,_day(day)
{}
friend inline istream& operator>>(istream& cin, Date& d);
friend inline ostream& operator<<(ostream& cout, Date& d);
//获取月份日期函数
int GetMonthDays(int year, int month);
//+=运算符重载
Date& operator+=(int days);
//+运算符重载
Date operator+(int days)
{
Date tmp(*this);
//复用+=重载
tmp += days;
return tmp;
}
//赋值运算符重载
Date& operator=(const Date& d);
//前置++运算符重载
Date& operator++()
{
//复用+=重载
*this += 1;
return *this;
}
//后置++运算符重载
Date operator++(int)
{
//复用+=函数
Date tmp(*this);
tmp += 1;
return tmp;
}
//>=运算符重载
bool operator>=(const Date& d) const;
//<=运算符重载
bool operator<=(const Date& d) const;
//<运算符重载
bool operator<(const Date& d) const
{
//<的对立事件为>=,故直接对>=取反
return !(*this >= d);
}
//>运算符重载
bool operator>(const Date& d) const
{
//>的对立事件为<=,故直接对<=取反
return !(*this <= d);
}
//==运算符重载
bool operator==(const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
//!=运算符重载
bool operator!=(const Date& d) const
{
//!=的对立事件为==,故直接对==取反
return !(*this == d);
}
//-=运算符重载
Date& operator-=(int days);
//-运算符重载
Date operator-(int days)
{
Date tmp(*this);
tmp._day -= days;
return tmp;
}
//前置--运算符重载
Date& operator--()
{
//复用-=重载函数
*this -= 1;
return *this;
}
//后置--运算符重载
Date operator--(int)
{
//复用-=重载
Date tmp(*this);
*this -= 1;
return tmp;
}
//日期-日期
int operator-(const Date& d);
};
inline ostream& operator<<(ostream& cout, Date& d)
{
cout << d._year << "/" << d._month << "/" << d._day << endl;
return cout;
}
inline istream& operator>>(istream& cin, Date& d)
{
cin >> d._year >> d._month >> d._day;
return cin;
}
//实现文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"
//获取月份日期函数
int Date::GetMonthDays(int year, int month)
{
int monthDays[] = { 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 monthDays[month] + 1;
}
return monthDays[month];
}
//+=运算符重载
Date& Date::operator+=(int days)
{
_day += days;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//>=运算符重载
bool Date::operator>=(const Date& d) const
{
//如果年大就直接返回true
if (_year>d._year)
{
return true;
}
else if(_year == d._year && _month > d._month)//年相等时比较月份,月份大就直接返回true
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)//年相等,月份相等时,天大就直接返回true
{
return true;
}
else//其他情况均返回false
{
return false;
}
}
//<=运算符重载
bool Date::operator<=(const Date& d) const
{
//如果年小就直接返回true
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)//年相等时比较月份,月份小就直接返回true
{
return true;
}
else if (_year == d._year && _month == d._month && _day < d._day)//年相等,月份相等时,天小就直接返回true
{
return true;
}
else//其他情况均返回false
{
return false;
}
}
//-=运算符重载
Date& Date::operator-=(int days)
{
_day -= days;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDays(_year, _month);
}
return *this;
}
//日期-日期
int Date::operator-(const Date& d)
{
Date maxYear = *this;
Date minYear = d;
int flag = 1;
if (maxYear < minYear)
{
maxYear = d;
minYear = *this;
flag = -1;
}
int count = 0;
while (maxYear != minYear)
{
count++;
++minYear;
}
return count * flag;
}
//测试文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date_class.h"
int main()
{
Date d;
Date d1(d);
Date d2(2023, 1, 1);
d--;
cout << d;
cout << (d != d1) << endl;
cout << (d >= d1) << endl;
Date d3;
d3 = --d2;
cout << d3;
Date d4(2023, 2, 7);
d4 -= 100;
cout << d4;
cout << d4 - d1 << endl;
Date d5;
Date d6;
cin >> d5 >> d6;
cout << d5 << d6 << endl;
return 0;
}
输入:
2024 3 23
2023 2 23
输出结果:
2024/3/22
1
0
2022/12/31
2022/10/30
-510
2024/3/23
2023/2/23