【C++】类的默认成员函数

类的默认成员函数

  • 类的六个默认成员函数
  • 构造函数
    • 构造函数的概念
    • 构造函数的特性
  • 析构函数
    • 析构函数的概念
    • 析构函数的特性
  • 构造函数与析构函数的调用顺序
  • 拷贝构造
  • 拷贝构造的概念
  • 拷贝构造的特性
  • 赋值运算符重载
    • 运算符重载
    • 赋值运算符重载
    • 前置++与后置++重载
    • 输入输出流重载
  • const修饰成员
  • 实现完整的日期系统
  • 取地址操作符重载
  • const取地址操作符重载

类的六个默认成员函数

当一个类中什么成员都没有时被称为空类。
空类:即任何类在什么都不写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

默认成员函数
初始化和清理
构造函数主要完成初始化工作
析构函数主要完成清理工作
拷贝赋值
拷贝构造是使用同类对象初始化创建对象
赋值重载主要是把一个对象赋值给另一个对象
取地址重载
主要是普通对象和const对象取地址,这俩个很少会自己实现

构造函数

构造函数的概念

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取地址操作符一般不需要重载,使用编译器默认取地址重载即可,只有存在默认情况才才需要重载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/27308.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高精度减法

高精度减法 两个高精度整数的减法题目思路实现代码实现 两个任意符号的高精度加减法 两个高精度整数的减法 高精度指的是数字的大小非常非常大&#xff0c;最多能有10的5次方 的 位数。 本次计算的两个数均为 正数&#xff0c;如何求负数会在最后提到。 题目 给定两个正整数…

解决浏览器缩放的时候,重新设置滚动条的位置,使页面滚动条固定悬浮在页面底部

项目场景&#xff1a; 浏览器调试页面兼容页面时&#xff0c;缩放页面宽度&#xff0c;整体超出时滚动条出现在页面最底部&#xff0c;不是悬浮在页面下面&#xff0c;只有滚动到最底部才出现&#xff0c;需要的是悬浮在页面底部&#xff0c;不是滚动到最下面才出现 解决方案…

java面试整合全套

什么是Java &#xff08;定义 优点&#xff09; java是一个平台&#xff0c;由jvm和Java应用编程接口构成的一门面向编程语言。 不仅吸收了C语言的各种优点&#xff0c;还摒弃了c语言里面的多继承,指针等概念&#xff0c;因此java的特征主要有功能强大和简单易用的特征。 jav…

FPGA Verilog模块化设计入门篇一

随着电子技术的快速发展&#xff0c;现场可编程门阵列&#xff08;FPGA&#xff09;已成为现代电子系统设计中不可或缺的一部分。FPGA的灵活性、可重构性和高性能使得它成为处理复杂算法、加速数据处理和实现特定功能的理想选择。然而&#xff0c;随着系统复杂性的增加&#xf…

go-zero整合Excelize并实现Excel导入导出

go-zero整合Excelize并实现Excel导入导出 本教程基于go-zero微服务入门教程&#xff0c;项目工程结构同上一个教程。 本教程主要实现go-zero框架整合Excelize&#xff0c;并暴露接口实现Excel模板下载、Excel导入、Excel导出。 go-zero微服务入门教程&#xff1a;https://blo…

Mysql学习(九)——存储引擎

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 七、存储引擎7.1 MySQL体系结构7.2 存储引擎简介7.3 存储引擎特点7.4 存储引擎选择7.5 总结 七、存储引擎 7.1 MySQL体系结构 连接层&#xff1a;最上层是一些客户…

自然语言处理领域的重大挑战:解码器 Transformer 的局限性

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

聚焦赛宁网安竞赛平台+赛事服务,引领网络安全竞赛新潮流

第八届XCTF总决赛将在2024年6月22日于中国成都震撼开启&#xff0c;本届总决赛分为个人Live Solo和团队KOH巅峰对决两个赛道&#xff0c;从个人和团队多角度全方位考察参赛人员的竞技水平。 巅峰对决 智慧的火花在此碰撞 个人Live Solo赛制 Live Solo赛分为晋级赛和Solo赛。…

贝壳APP渗透测试WP

前期配置 环境说明 使用PIXEL 4手机&#xff0c;为Android 12系统 APP名为贝壳找房&#xff0c;包名com.lianjia.beike&#xff0c;版本号3.01.10&#xff0c;截至2024/05/07为最新版&#xff0c;小米应用市场下载 绕过反Frida机制 可以参考往期推送&#xff0c;《绕过最新…

2分钟用手机开发一个ChatBot

前言&#xff1a; 在上一期&#xff0c;我们测评了CodeFlying&#xff0c;用它开发出了一个复杂推文管理系统&#xff0c;然后体验了一下它的热门应用&#xff1a;AI智能机器人。今天咱就继续用CodeFlying来开发一个属于我们自己的聊天机器人。 老规矩&#xff0c;我们先在手机…

【qt】平面CAD(计算机辅助设计 )项目 上

CAD 一.前言二.界面设计三.提升类四.接受槽函数五.实现图形action1.矩形2.椭圆3.圆形4.三角形5.梯形6.直线7.文本 六.总结 一.前言 用我们上节课刚刚学过的GraphicsView架构来绘制一个可以交互的CAD项目! 效果图: 二.界面设计 添加2个工具栏 需要蔬菜的dd我! 添加action: …

遗传算法求解车间调度问题(附python代码)

背景介绍 车间调度问题&#xff08;Job Shop Scheduling Problem, JSSP&#xff09;是一类经典的组合优化问题&#xff0c;它在制造业和生产管理中有着广泛的应用。JSSP 的目标是对车间中的一系列作业进行排程&#xff0c;以使得作业在不同机器上的加工顺序是最优的&#xff0…

万相台的功能是什么?如何使用万相台?

1.特点&#xff1a; 万相台是一个智能渠道&#xff0c;可控性弱&#xff0c;高转化&#xff0c;人群&关键词是黑盒&#xff1b; 2.场景多&#xff1a; 有拉新快、活动加速、上新快、货品加速、活动加速、多目标直投、全站推等&#xff1b; 3.扣费逻辑&#xff1a;cpc付…

Sm4【国密4加密解密】

当我们开发金融、国企、政府信息系统时&#xff0c;不仅要符合网络安全的等保二级、等保三级&#xff0c;还要求符合国密的安全要求&#xff0c;等保测评已经实行很久了&#xff0c;而国密测评近两年才刚开始。那什么是密码/国密&#xff1f;什么是密评&#xff1f;本文就关于密…

Linux:线程概念 线程控制

Linux&#xff1a;线程概念 & 线程控制 线程概念轻量级进程 LWP页表 线程控制POSIX 线程库 - ptherad线程创建pthread_createpthread_self 线程退出pthread_exitpthread_cancelpthread_joinpthread_detach 线程架构线程与地址空间线程与pthread动态库 线程的优缺点 线程概念…

机器学习与数据挖掘知识点总结(二)分类算法

目录 1、什么是数据挖掘 2、为什么要有数据挖掘 3、数据挖掘用在分类任务中的算法 朴素贝叶斯算法 svm支持向量机算法 PCA主成分分析算法 k-means算法 决策树 1、什么是数据挖掘 数据挖掘是从大量数据中发现隐藏在其中的模式、关系和规律的过程。它利用统计学、机器学…

14.shell awk数组

awk数组 awk数组awk数组示例Nginx日志分析 awk数组 1.什么是awk数组 数组其实也算是变量,传统的变量只能存储一个值,但数组可以存储多个值 2.awk数组应用场景 通常用来统计、比如:统计网站访问TOP10、网站url访问TOP10等等 3.awk数组统计技巧 1.在awk中,使用数组时,不仅可以…

Interview preparation--RabbitMQ

AMQP AMQP(Advanced Message Queueing protocol). 高级消息队列协议&#xff0c;是进程之间床底一步新消息的网络协议AMQP工作原理如下&#xff1a; 发布者&#xff08;Publisher&#xff09;发布消息&#xff08;Message&#xff09;经过交换机&#xff08;Exchange&#xff…

新视窗新一代物业管理系统 GetCertificateInfoByStudentId SQL注入漏洞复现

0x01 产品简介 新视窗物业管理系统属于专家型的物业管理软件,能够给物业公司内部管理提供全面的解决方案,具有房产管理、客户管理、租赁管理、仪表管理、财务收费、发票管理、合同管理、仓储管理、设施设备管理、客户服务管理、会员管理、人事管理、资产管理、日常办公、档案…

HTML+CSS 动态卡片

效果演示 实现了一个带有动态背景和图片放大效果的卡片展示。卡片的背景是由两种颜色交替组成的斜线条纹&#xff0c;同时背景会以一定速度循环滚动。当鼠标悬停在卡片上时&#xff0c;卡片的图片会放大&#xff0c;并且卡片的背景会变为彩色。 Code HTML <!DOCTYPE html&…