【C++】类的默认成员函数
类的默认成员函数
- 类的六个默认成员函数
- 构造函数
- 构造函数的概念
- 构造函数的特性
- 析构函数
- 析构函数的概念
- 析构函数的特性
- 构造函数与析构函数的调用顺序
- 拷贝构造
- 拷贝构造的概念
- 拷贝构造的特性
- 赋值运算符重载
- 运算符重载
- 赋值运算符重载
- 前置++与后置++重载
- 输入输出流重载
- const修饰成员
- 实现完整的日期系统
- 取地址操作符重载
- const取地址操作符重载
类的六个默认成员函数
当一个类中什么成员都没有时被称为空类。
空类:即任何类在什么都不写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。
构造函数
构造函数的概念
class Data
{
public:void Init(int year = 2000, 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(void)
{Data d1;d1.Init();d1.Print();Data d2;d2.Init(2024,6,8);d2.Print();return 0;
}
每次创建代码时,都需有初始化代码,使得工作量加大,引入构造函数可以进行初始化工作。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象的整个生命周期内只使用一次。
构造函数的特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1.函数名与类名相同
2.无返回值(也不需要写void)
3.对象实例化时编译器会自动调用对应的构造函数
class Data
{
public:Data(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main(void)
{Data d1();return 0;
}
4.构造函数可以重载
class Data
{
public://带参数的构造函数Data(int year, int month, int day){_year = year;_month = month;_day = day;}//不带参数的构造函数Data(){_year = 2000;_month = 1;_day = 1;}
private:int _year;int _month;int _day;
};int main(void)
{//调用带参数的构造函数Data d1(2024,6,8);//调用不带参数的构造函数Data d2;return 0;
}
构造函数支持重载的原因是:可以存在不同的初始化情况。
【注意】如果公共无参构造函数创建对象时,对象后面不用接括号,否则会变成函数声明。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户定义编译器将不再生成。
class Data
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};int main(void)
{Data d1;d1.Print();return 0;
}
编译器默认初始化成随机值:
这里需要说明一下:
C++里将类型分成俩类:
1.内置类型
内置类型属于基本类型,是语言本身定义的基础类型,例如int、char、double、指针等等
2.自定义类型
用struct、calss等定义的类型
编译器自动默认生成构造函数,内置类型不做处理,自定义类型会去调用其默认构造。(这里需要注意的是,不同的编译器有不同的处理方法,有些编译器也会处理,但是至少部分编译器个性化处理,不是所有的编译器都会处理)
总结:
1.一般情况下,有内置类型成员的,就需要自己写构造函数,不能让编译器自己生成
2.全部都是自定义类型成员,可以考虑让编译器自己生成。(一个经典的算法题:使用俩个栈实现队列就可以使用默认构造函数)
6.C++11中,在成员声明的时候可以给缺省值
class Data
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year = 2000;int _month = 1;int _day = 1;
};
【注意】这里不是初始化
由于这里只能存在成员变量的声明,没有开空间,这里给的是默认的缺省值,给编译器生成默认构造函数用。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数、并且默认构造函数只能有一个。
【注意】无参构造函数、全缺省默认构造函数、如果我们自己没写,编译器默认生成的构造函数都可以认为是默认构造函数。
class Data
{
public://无参构造函数Data(){_year = 2000;_month = 1;_day = 1;}//全参构造函数Data(int year = 2000,int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
int main(void)
{Data d1;return 0;
}
不传参就可以调用的就是默认构造函数。
析构函数
析构函数的概念
析构函数是特殊的成员函数,析构函数与构造函数的功能相反,析构函数不是完全对对象本身的销毁,局部对象销毁工作时编译器完成的,而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
析构函数的特性
1.析构函数名实在类名前面加上字符~
2.无参数无返回值
class Data
{
public:Data(int year = 2000, int month = 1, int day = 1){this->_year = year;this->_month = month;this->_day = day;}~Data(){_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};
3.一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数。【注意】析构函数不能重载。
4。对象声明周期结束时,C++编译器系统自动调用析构函数。
#include<iostream>
using namespace std;
class Data
{
public:Data(int year = 2000, int month = 1, int day = 1){cout << "Data" << endl;this->_year = year;this->_month = month;this->_day = day;}~Data(){cout << "~Data" << endl;_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};
int main(void)
{Data d1;return 0;
}
5.系统自动生成的默认构造函数:
(1).内置类型成员不做处理。
(2).自定义类型会去调用他的析构函数。
下面这段代码是本人实现的:
class Stack
{
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_size = 1;}~Stack(){free(_arr);_arr = nullptr;_size = 1;_capacity = 0;}void Push(int x){if (this->_capacity == this->_size){int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);if (arr == nullptr){perror("realloc fail");return;}this->_arr = arr;this->_capacity *= 2;}this->_arr[this->_size - 1] = x;this->_size++;}void Pop(){this->_arr[this->_size - 1] = 0;this->_size--;}
private:int* _arr;int _size;int _capacity;
};
int main(void)
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);s1.Pop();return 0;
}
这里可以发现手动实现的析构函数可以根据自己的要求满足实现。
class Stack
{
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_size = 1;}void Push(int x){if (this->_capacity == this->_size){int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);if (arr == nullptr){perror("realloc fail");return;}this->_arr = arr;this->_capacity *= 2;}this->_arr[this->_size - 1] = x;this->_size++;}void Pop(){this->_arr[this->_size - 1] = 0;this->_size--;}
private:int* _arr;int _size;int _capacity;
};
int main(void)
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);s1.Pop();return 0;
}
所以,一般情况下如果没有动态内存申请,析构函数可以不写,例如:Data类;但是如果有动态内存申请,就需要显式写析构函数释放资源,否则会造成内存泄漏。例如:栈的实现。
构造函数与析构函数的调用顺序
-
类的析构函数调用一般按照构造函数调用的相反顺序调用,但是需要注意static对象的存在,因为static改变了对象的生存作用域,需要等待程序结束后才可析构释放对象。
-
全局对象先于局部对象进行构造。
-
局部对象按照顺序进行构造,无论是否为static对象。
-
static修饰的对象会在局部对象析构后进行析构。
拷贝构造
拷贝构造的概念
创建对象时,需要创建一个与已存在对象一模一样的新对象,就需要用到拷贝构造。
拷贝构造:只有单个形参,该形参是对本类型对象的引用(一般常用const修饰),在用已存在的类型对象创建新对象时由编译器自动调用。
拷贝构造的特性
拷贝构造函数也是特殊的成员函数,其特征如下:
1.拷贝构造函数时构造函数的一个重载形式,所有书写格式与构造函数类似,但是参数类型不同。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器之间报错,因为会引发无穷递归调用。
观察下面代码:
class Data
{
public:Data(int year = 2000, int month = 1, int day = 1){cout << "Data" << endl;this->_year = year;this->_month = month;this->_day = day;}~Data(){cout << "~Data" << endl;_year = 0;_month = 0;_day = 0;}//拷贝构造函数//传值Data(Data d) {_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main(void)
{Data d1;Data d2(d1);return 0;
}
首先编译器会自动报错,原因是:
当我们以传值方式传参时,需要先建立一个临时拷贝,而在建立临时拷贝也需要传参,再以传值方式传参时,又需要建立一个临时拷贝…
这种无穷递归,编译器会强制检查,解决的办法有俩种:
1.使用指针(内置类型)
2.使用引用(推荐)
【注意】使用引用时需要注意赋值方向(d._year = _year);就是错误代码。
由于传引用的值不用改变,所有可以使用const缩小权限。
正确代码如下:
class Data
{
public:Data(int year = 2000, int month = 1, int day = 1){this->_year = year;this->_month = month;this->_day = day;}~Data(){_year = 0;_month = 0;_day = 0;}//拷贝构造函数//传值Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main(void)
{Data d1;Data d2(d1);return 0;
}
3.如果没有显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫浅拷贝,或者值拷贝。
(1)内置类型成员完成值拷贝、浅拷贝。
(2)自定义类型成员会调用他的拷贝构造。
观察代码:
class Stack
{
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_size = 1;}void Push(int x){if (this->_capacity == this->_size){int* arr = (int*)realloc(this->_arr, sizeof(int) * this->_capacity * 2);if (arr == nullptr){perror("realloc fail");return;}this->_arr = arr;this->_capacity *= 2;}this->_arr[this->_size - 1] = x;this->_size++;}void Pop(){this->_arr[this->_size - 1] = 0;this->_size--;}
private:int* _arr;int _size;int _capacity;
};
int main(void)
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);s1.Pop();Stack s2(s1);return 0;
}
以栈为例,栈不可以使用默认拷贝构造,栈默认生成的拷贝构造会将俩个指针指向同一个栈,在析构时,后面拷贝的指针会先析构,而前面被拷贝的指针会后析构,一个堆区被析构俩次,编译器会报错,同时如果修改其中一个值,会影响另一个。
栈拷贝构造的正确代码是:
Stack(const Stack& s){_arr = (int*)malloc(sizeof(int) * s._capacity);if (_arr == nullptr){perror("malloc fail");return;}memcpy(_arr, s._arr, sizeof(int) * s._size);_size = s._size;_capacity = s._capacity;}
需要动态开辟的都需要自己实现深拷贝,而像日期类可以不写拷贝构造,默认生成的拷贝构造就可以用。
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
- 内置类型可以通过编译器计算,而自定义类型也可以向内置类型一样,需要通过运算符重载,进行加、减、比较等。
以Date日期类举例:是否使用重载运算符,是观察这个运算符对这个类是否有意义,例如:日期相减可以计算相差天数,而日期相加却没有意义。
下面演示日期比较大小:
class Date
{
public://Date构造Date(int year = 2000,int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date默认析构,默认拷贝//
//private:int _year;int _month;int _day;
};bool operator>(const Date& d1, const Date& d2)
{if (d1._year > d2._year){return true;}else if (d1._year == d2._year && d1._month > d2._month){return true;}else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day){return true;}return false;
}int main(void)
{Date d1(2024, 6, 10);Date d2(2024, 5, 20);if (d1 > d2){cout << "d1 > d2" << endl;}else{cout << "d1 < d2" << endl;}return 0;
}
这里需要注意的是编译器在编译期间将d1>d2转换成operator>(d1,d2),所以将d1>d2写成operator>(d1,d1)是相同的,第二种方式相当于调用函数。
int main(void)
{Date d1(2024, 6, 10);Date d2(2024, 5, 20);//第一种方式if (d1 > d2){cout << "d1 > d2" << endl;}else{cout << "d1 < d2" << endl;}//第二种方式if (operator>(d1,d2)){cout << "d1 > d2" << endl;}else{cout << "d1 < d2" << endl;}return 0;
}
在反汇编的角度观察:
本质上二者都是相同的,编译器在编译期间将第一种方式转换为第二种方式,然后进行call
但是观察刚才的代码,如果在类中将成员变量改为私有,那么编译器会报错,在类外的函数是无法访问私有(private)和保护(protected),解决的办法有俩种:1.使用友元(建议能不用就不用,友元会破坏封装);2放在类内,将其视为成员函数。
下面演示将运算符重载视为成员函数:
class Date
{
public://Date构造Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date默认析构,默认拷贝//比较大小的运算符重载bool 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 == d._month && _day > d._day){return true;}return false;}
private:int _year;int _month;int _day;
};int main(void)
{Date d1(2024, 6, 10);Date d2(2024, 5, 20);//第一种方式if (d1 > d2){cout << "d1 > d2" << endl;}else{cout << "d1 < d2" << endl;}//第二种方式if (d1.operator>(d2)){cout << "d1 > d2" << endl;}else{cout << "d1 < d2" << endl;}return 0;
}
将运算符重载函数放入类内时,参数只有一个,另外一个时隐藏的this。同时d1>d2准换为函数为d1.operator(d2),和使用成员函数时相同。
值得注意的是,写出成员函数更加方便,也更加容易理解。
【注意】
1.不能通过连接其他符号来创建新的操作符,例如:operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整数+,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数少1,因为成员函数的第一个参数为隐藏的this
5. “.*” “::” “sizeof” “?:” "."注意以上五个运算符不能重载。
6.操作符时几个操作数,重载函数就有几个参数。
赋值运算符重载
如何在类中实现一个赋值运算符重载?
//实现赋值运算符重载void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
在这里可以发现赋值运算符重载与拷贝构造类似,但是值得注意的是,二者有本质上的区别,我们需要理一理二者的概念:
拷贝构造函数:函数用一个已经存在的对象初始化另一个对象
运算符重载函数:已经存在的俩个对象之间的复制拷贝
通过理解二者的概念,那么通过哪种方式调用呢?
int main(void)
{//调用拷贝构造函数Date d1(2024, 6, 10);Date d2 = d1;//调用运算符重载函数Date d3(2024, 5, 20);Date d4(2003, 2, 3);d3 = d4;return 0;
}
初步认识赋值运算符函数,那么赋值运算符是如何使用的呢?
int i , j , k;i = j = k = 10;
赋值运算符可以进行连续赋值:
所以我们可以将代码进一步更改:
//实现赋值运算符重载Date operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
this指针不可以再形参出现,但是可以在成员函数内部出现。
但是在使用Date作为返回值时,每次赋值都会调用拷贝构造,所以建议直接使用引用返回。
代码如下:
Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
因为传值返回,值在函数结束后会销毁,返回的是值的拷贝,而传引用返回,返回的是别名,是*this这个指针在未被销毁时所在地址的值的别名。
this是参数,也是进行传参的,this的生命周期实在函数调用结束后销毁,相当于在函数建立时先进行push,压栈,然后函数在结束后销毁,this也会被pop,虽然this这个指针被销毁了,但是this这个指针所指向的值没有被销毁。
会不会存在一种情况,是自己给自己赋值:
d1 = d2;
所以为了防止这种情况,需要对代码进行更改:
//实现赋值运算符重载Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
总结:
1.赋值运算符重载格式:
- 参数类型:const T& ,传递引用可以提高传参效率
- 返回值类型:T& ,返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值
- 检查是否自己给自己赋值
- 返回*this,要符合连续赋值的含义
2.赋值运算符只能重载成类的成员函数不能重载成全局函数,因为类中存在默认赋值运算符重载,会与之冲突。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值得方式逐字节拷贝。
【注意】默认生成赋值重载跟拷贝构造行为一样:内置类型成员仅值拷贝,浅拷贝;自定义类型成员会去调用其赋值重载。
例如:Date日期类、Myqueue不需要自己实现赋值重载,stack需要自己实现,因为默认生成的是浅拷贝。
前置++与后置++重载
//前置++Date& operator++(){_day = _day + 1;return *this;}
【注意】使用前置++时,需要注意前置++时返回++以后的对象。
//后置++Date operator++(int){Date tmp(*this);_day = _day + 1;return tmp;}
前置++与后置++都是一元运算符为了让让前置++与后置++形参正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时,该参数不用传递,编译器会自动传递。
【注意】使用后置++时,需要注意后置++返回++以前的对象,所以需要tmp保存一份,tmp是临时变量,返回时必须需要进行拷贝构造。
输入输出流重载
int i = 10;printf("%d", i);
我们知道printf可以打印内置类型,但是不可以打印自定义类型。
如果不使用调用函数的方式,是如何实现打印自定义函数呢?
在C++中支持使用流插入流提取运算符重载的方式进行打印自定义类型。
在C++reference中介绍cout是ostream类型的对象,cin是istream类型的对象。
在C++中,内置类型是初创C++时使用流插入和流提取定义的成员函数。
运算符重载是为了让自定义类型支持运算符:
1.可以直接支持内置类型的是库里实现的
2.可以直接支持自定义识别类型是因为函数重载
实现流插入自定义:
//自定义流插入
void Date::operator<<(ostream& out)
{out << _year << "年" << _month << "月" << _day << "日" << endl;
}
void TestDate2()
{int i = 10;cout << i << endl;Date d1(2024,6,12);d1 << cout;
}
从我们初步实现流插入可以发现我们所实现的运算符不符合运算重载。
cout << d1;
由于cout是终端、控制台,要实现只能在库里实现,流插入不能写成成员函数,因为Date类对象默认占用第一个参数,就是主操作数,这种写法写出来只能是d1<<cout,但是这不符合使用习惯。
而全局函数不会占用第一个参数,没有默认参数,但是访问不了私有问题,解决办法有俩个:
1.写成公有的成员函数
class Date
{
public://公有的成员函数int GetYear(){return _year;}int GetMonth(){return _month;}int GetDay(){return _day;}
private:int _year;int _month;int _day;
};
//全局的流插入操作符
void operator<<(ostream& out, Date& d)
{out << d.GetYear() << "年" << d.GetMonth() << "月" << d.GetDay() << "日" << endl;
}
2.使用友元函数(后续讲解)
class Date
{
public://友元函数声明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;
}
这里使用友元函数比调用公有的函数方便一些。
在实际使用流插入的时候可以进行连续插入
cout << d1 << d2 << d3;
则需要更换返回值来进行连续插入
//全局的流插入操作符
ostream& operator<<(ostream& out, Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
同时ostream对象可以使用const修饰,因为其在流入控制台过程中没有被修改。
最后整合一下流提取运算符重载函数:
class Date
{
public://友元函数声明friend ostream& operator<<(ostream& out, const Date& d);
private:int _year;int _month;int _day;
};
//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" ;return out;
}
通过对流插入的了解,我们可以试着写出流提取运算符重载:
class Date
{
public://友元函数声明friend istream& operator>>(istream& in, Date& d);
private:int _year;int _month;int _day;
};
//全局的流提取操作符
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
值得注意的是:流提取是的参数是不可以加const的。istream不使用const修饰的原因是:当我们从输入流中读取数据时,流的状态会发生变化,例如读取位置会向前移动;对象不使用const修饰的原因是:输入流是可变的、输入流应该允许被修改。
总的来讲,流是支持任何形式的输入与输出。
const修饰成员
Date d1(2022,2,2);d1.print();const Date.d2(2011,10,10);d2.print();
假设d1与d2俩个对象都调用print成员函数时
void print();
d1可以调用成功,而d2却调用不成功。
d1在调用print成员函数时,Date的权限平移;而d2在调用print成员函数时,Date的权限被放大,由起初的Date的不可被修改,变成可修改时不被允许的。
那么如何可以更改呢?
void print(const Date* this);
如果可以这样写就可以实现d1调用成员函数,但是this指针是不能作为形参出现的。
所以在C++中对此做了新的说明:
将const修饰的成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
void print() const;
const修饰的是*this,如果成员函数加上const,普通与const对象都可以调用,但并不是所有的成员函数都可以加上const修饰,要修改成员变量的成员函数不可以加,只要成员函数内部不修改成员变量,都应该加const,这样const对象和普通对象都可以调用。
实现完整的日期系统
- Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;class Date
{
public://全缺省的构造函数Date(int year = 2000, int month = 1, int day = 1);//析构函数~Date();//拷贝构造函数Date(const Date& d);//赋值运算符重载Date& operator=(const Date& d);//打印void Print() const{cout << _year << "/" << _month << "/" << _day << endl;}//获取某年某月的天数int GetMonthDay(int year, int month) const;//日期+=天数Date& operator+=(int day);//日期+天数Date operator+(const int day) const;//日期-=天数Date& operator-=(int day);//日期-天数Date operator-(const int day) const;//前置++Date& operator++();//后置++Date operator++(int);//前置--Date operator--();//后置--Date operator--(int);//>运算符重载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;//日期-日期int operator-(Date& d) const;//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);//全局的流提取操作符friend istream& operator>>(istream& in, Date& d);
private:int _year;int _month;int _day;
};//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d);
//全局的流提取操作符
istream& operator>>(istream& in, Date& d);
- Date.cpp
#include"Date.h"//全缺省的构造函数
Date::Date(int year, int month, int day)
{if (month > 0 && month < 13&& day > 0 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "日期输入故障" << endl;assert(false);}
}
//析构函数
Date::~Date()
{_year = 0;_month = 0;_day = 0;
}
//拷贝构造函数
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
//获取某年某月的天数
int Date::GetMonthDay(int year, int month) const
{int MonthArr[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 MonthArr[month];
}
//日期+=天数
Date& Date::operator+=(int day)
{if (day <= 0){day = -day;*this -= day;day = 0;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month > 12){++_year;_month = 1;}}return *this;
}
//日期+天数
Date Date::operator+(int day) const
{Date tmp = *this;tmp += day;return tmp;
}
//日期-=天数
Date& Date::operator-=(int day)
{if (day < 0){day = -day;*this += day;day = 0;}_day -= day;while (_day <= 0){--_month;if (_month < 1){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}
//日期-天数
Date Date::operator-(int day) const
{Date tmp = *this;tmp -= day;return tmp;
}//前置++
Date& Date::operator++()
{*this += 1;return *this;
}
//后置++
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}
//前置--
Date Date::operator--()
{*this -= 1;return *this;
}
//后置--
Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}
//>运算符重载
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
{if (_year == d._year && _month == d._month && _day == d._day){return true;}return false;
}
//>=运算符重载
bool Date::operator>=(const Date& d) const
{if (*this > d || *this == d){return true;}return false;
}
//<运算符重载
bool Date::operator<(const Date& d) const
{if (!(*this >= d)){return true;}return false;
}
//<=运算符重载
bool Date::operator<=(const Date& d) const
{if (!(*this > d)){return true;}return false;
}//日期-日期
int Date::operator-(Date& d) const
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = 1;flag = -1;}int n = 0;while (min < max){++min;++n;}return n * flag;
}//全局的流插入操作符
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" ;return out;
}
//全局的流提取操作符
istream& operator>>(istream& in, Date& d)
{int year = d._year;int month = d._month;int day = d._day;if (month > 0 && month < 13&& day > 0 && day <= d.GetMonthDay(year, month)){in >> year >> month >> day;}else{cout << "日期输入故障" << endl;assert(false);}return in;
}
取地址操作符重载
class Date
{
public:Date* operator&(){return this;}
private:int _year;int _month;int _day;
};
const取地址操作符重载
class Date
{
public:const Date* operator&() const{return this;}
private:int _year;int _month;int _day;
};
取地址与const取地址操作符一般不需要重载,使用编译器默认取地址重载即可,只有存在默认情况才才需要重载。
相关文章:

【C++】类的默认成员函数
类的默认成员函数 类的六个默认成员函数构造函数构造函数的概念构造函数的特性 析构函数析构函数的概念析构函数的特性 构造函数与析构函数的调用顺序拷贝构造拷贝构造的概念拷贝构造的特性赋值运算符重载运算符重载赋值运算符重载前置与后置重载输入输出流重载 const修饰成员实…...

归并排序!
归并排序 https://articles.zsxq.com/id_g23e5o3lg87e.html 目录 归并排序算法思想命名由来算法描述sortList函数mergeSort函数 源代码 算法思想 通过将当前乱序的数组分成两个部分,分别进行「递归调用」,利用两个指针将数据元素以此比较,选…...

深入探讨:Spring与MyBatis中的连接池与缓存机制
深入探讨:Spring与MyBatis中的连接池与缓存机制 引言 在现代应用程序开发中,性能优化是一个永恒的话题。而在企业级Java应用开发中,Spring和MyBatis是两种非常流行的框架,它们的连接池和缓存机制对应用程序的性能有着至关重要的…...

[C#]使用C#部署yolov10的目标检测tensorrt模型
【测试通过环境】 win10 x64vs2019 cuda11.7cudnn8.8.0 TensorRT-8.6.1.6 opencvsharp4.9.0 .NET Framework4.7.2 NVIDIA GeForce RTX 2070 Super cuda和tensorrt版本和上述环境版本不一样的需要重新编译TensorRtExtern.dll,TensorRtExtern源码地址:T…...

Linux CFS 调度器 (1):概述
文章目录 1. 前言2. CFS 调度器2.1 概述2.2 一些实现细节2.3 运行队列:红黑树2.4 一些特征2.5 调度策略2.6 调度器类别2.7 扩展:组调度 3. 参考资料 1. 前言 限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失ÿ…...

HBase中Master初始化错误~
ERROR:org.apache.hadoop.hbase.PleaseHoldException:Master is initializing 1、停止HBase运行 2、启动zookeeper中的zkCli.sh服务 ./zookeeper/bin/zkCli.sh 3、执行完毕显示以下结果,删除habse文件夹 4、重新启动HBase即可。...

Hive on Spark版本兼容性
Hive on Spark仅在特定版本的Spark上进行测试,因此给定版本的Hive只能保证与特定版本的Spark一起工作。其他版本的Spark可能与给定版本的Hive一起工作,但不能保证。以下是Hive版本及其对应的Spark版本列表: 详情参考官方文档:http…...

grep命令知多少
引言 1. grep命令的重要性 在Linux系统中,grep是一个不可或缺的文本处理工具,它允许用户快速搜索文件中的文本模式。这个命令的名称来源于Global Regular Expression Print,即全局正则表达式打印,它源自UNIX早期的ed文本编辑器。…...

[java]windows和linux下jdk1.8安装包所有版本系列下载地址汇总
【windows jdk1.9系列下载地址】 序号java版本下载地址1java-jdk9-jdk-9.0.1-windows-x64-bin.zip点我下载 【windows jdk1.8系列下载地址】 序号java版本下载地址1java-jdk1.8-jdk-8u202-windows-x64.zip点我下载2java-jdk1.8-jdk-8u201-windows-x64.zip点我下载3java-jdk1…...

Electron+Vue开源软件:洛雪音乐助手V2.8畅享海量免费歌曲
洛雪音乐助手是一款功能全面且完全免费的开源音乐软件,支持在Windows、Android和iOS平台上使用。 平台支持: 桌面版:采用Electron Vue技术栈开发,支持Windows 7及以上版本、Mac OS和Linux,具有广泛的用户群体覆盖。 …...

CAPL通过addTimeToMeasurementStartTime或者getLocalTime获取本地时间
文章目录 getLocalTimeaddTimeToMeasurementStartTimegetLocalTime long tm[9]; getLocalTime(tm); // now tm contains the following entries: // tm[0] = 3; (seconds) // tm[1] = 51; (minutes) // tm[2] = 16; (hours)...

谷歌上架,APP被移除了,没封号,换个包名还能重新提审上架?
对于在Google Play上架应用的开发者来说,尤其是那些矩阵式上架马甲包的开发者,可能已经遭遇过无数次应用被暂停或移除的情况了。通常这种情况下,账号也随之会被封,且大多数开发者认为,没有立马收到封号邮件的话&#x…...

Docker部署MaxKB 知识库(提高问答命中率)
前言 上一篇文章简单的介绍了下MaxKB,这一篇文章就讲如何部署MaxKB。 MaxKB实现逻辑也比较简单,如下图。 安装 修改Docker镜像源 由于不可抗力,部分源已经无法使用,需要修改以下的源地址来拉取镜像。如果是linux,…...

LeetCode739每日温度
题目描述 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。 解析 每次往栈中…...

【Qt】Qt中的几种Timer
1. QObject::startTimer int QObject::startTimer(int interval, Qt::TimerType timerType Qt::CoarseTimer) int QObject::startTimer(std::chrono::milliseconds time, Qt::TimerType timerType Qt::CoarseTimer)每次时间到了会调用虚函数timerEvent() 2. QTimer 3. QBa…...

Excel 多列组合内容循环展开
某表格 A 列是编号,其他列是用逗号分隔的意义不同的分类列 ABCDEFG1Assembly#ProductTypeUnit ConfigNominal CapacitySupply VoltageGenerationCase Construction23H1012290001CMD,P24,36FAA,B33H1012290002CMD,P48,60FA,BA,B43H1012290003CMD,P24,36B,C,D,EAA,B …...

Vue2+Element-ui实现el-table表格自适应高度
效果图 新建指令 Vue.directive(height, {inserted(el, _binding, vnode) {const paginationRef vnode.context.$refs.paginationRefconst calculateHeight () > {const windowHeight window.innerHeightconst topOffset el.getBoundingClientRect().topconst otherEle…...

【人工智能】开发AI可能获刑?加州1047草案详解
引言 随着人工智能(AI)技术的飞速发展,其应用领域不断扩展,但同时也引发了诸多争议和监管问题。近期,加州参议院以32比1的压倒性投票通过了1047号草案,又称《前沿人工智能模型安全可靠创新法案》。这一草案…...

机器学习二分类数据集预处理全流程实战讲解
本文概述 本文对weatherAUS数据集进行缺失值分析并剔除高缺失特征,合理填补剩余缺失值,利用相关性筛选关键特征,采用多种机器学习模型(如逻辑回归、随机森林等)在80%训练集上训练,并在20%测试集上预测明日降…...

大模型应用:LangChain-Golang核心模块使用
1.简介 LangChain是一个开源的框架,它提供了构建基于大模型的AI应用所需的模块和工具。它可以帮助开发者轻松地与大型语言模型(LLM)集成,实现文本生成、问答、翻译、对话等任务。LangChain的出现大大降低了AI应用开发的门槛,使得任何人都可以…...

【Tkinter界面】Canvas 图形绘制(03/5)
文章目录 一、说明二、画布和画布对象2.1 画布坐标系2.2 鼠标点中画布位置2.3 画布对象显示的顺序2.4 指定画布对象 三、你应该知道的画布对象操作3.1 什么是Tag3.2 操作Tag的函数 https://www.cnblogs.com/rainbow-tan/p/14852553.html 一、说明 Canvas(画布&…...

【CS.PL】Lua 编程之道: 基础语法和数据类型 - 进度16%
2 初级阶段 —— 基础语法和数据类型 文章目录 2 初级阶段 —— 基础语法和数据类型2.0 关键字(keywords) 🔥2.1 注释与标识符2.1.1 注释2.1.2 标识符 2.2 变量与赋值2.2.1 所有变量默认是全局变量 ≠ local, 有一个例外2.2.2 local变量是局部变量, 以end作为边界2.…...

centos7 xtrabackup mysql 基本测试(3)---虚拟机环境 安装mysql
centos7 xtrabackup mysql 基本测试(3)—虚拟机环境 安装mysql centos7 安装 mysql5.7 可以在运行安装程序之前导入密钥: sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022第一步、下载MySQL 安装包: sudo w…...

Java Native Interface 使用指南
我们知道Java本身的实现,很大一部分是用C写的。实际上,Java也允许我们和原生平台的代码进行交互。 Java定义了一个接口规范,就叫做Java Native Interface,通过这个接口规范,我们就可以让Java代码运行原生平台的代码。…...

代码随想录算法训练营第三十九天 | 62.不同路径、63. 不同路径 II、343. 整数拆分、96.不同的二叉搜索树
62.不同路径 题目链接:https://leetcode.cn/problems/unique-paths/ 文档讲解:https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE… 视频讲解:https://www.bilibili.com/video/BV1ve4y1x7Eu/ 思路 确定dp数组以及下标的含…...

C/C++函数指针、C#委托是什么?
函数指针 #include<stdio.h>//声明函数指针 typedef int(*Calc)(int a, int b); int Add(int a, int b) {return a b; } int Sub(int a, int b) {return a - b; }int main() {Calc funcPoint1 &Add;Calc funcPoint2 ⋐int x 120;int y 140;int z 0;z …...

红队攻防渗透技术实战流程:组件安全:JacksonFastJsonXStream
红队攻防渗透实战 1. 组件安全1.1 J2EE-组件Jackson-本地demo&CVE1.1.1 代码执行 (CVE-2020-8840)1.1.2 代码执行(CVE-2020-35728)1.2 J2EE-组件FastJson-本地demo&CVE1.2.1 FastJson <= 1.2.241.2.2 FastJson <= 1.2.471.2.3 FastJson <= 1.2.801.3 J2EE-组…...

Perl 语言学习进阶
一、如何深入 要深入学习Perl语言的库和框架,可以按照以下步骤进行: 了解Perl的核心模块:Perl有许多核心模块,它们提供了许多常用的功能。了解这些模块的功能和用法是深入学习Perl的第一步。一些常用的核心模块包括:S…...

LangGraph实战:从零分阶打造人工智能航空客服助手
❝ 通过本指南,你将学习构建一个专为航空公司设计的客服助手,它将协助用户查询旅行信息并规划行程。在此过程中,你将掌握如何利用LangGraph的中断机制、检查点技术以及更为复杂的状态管理功能,来优化你的助手工具,同时…...

R可视化:R语言基础图形合集
R语言基础图形合集 欢迎大家关注全网生信学习者系列: WX公zhong号:生信学习者Xiao hong书:生信学习者知hu:生信学习者CDSN:生信学习者2 基础图形可视化 数据分析的图形可视化是了解数据分布、波动和相关性等属性必…...