类和对象中,通过日期类来深入理解构造函数,析构函数,重载,拷贝构造,赋值重载,取地址重载,操作符重载



类的六个默认成员函数

在这里插入图片描述

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情 况下,都会自动生成下面6个默认成员函数。

class Date{};

image-20220523230651717

构造函数

构造函数的特性:

​ 构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主 要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定 义编译器将不再生成。
  6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参 构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数
  7. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但 是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵 用?? 解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现 编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数
  8. 成员变量的命名风格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
Date(int year)
{
// 这里的year到底是成员变量,还是函数形参?
year = year;
}
private:
int year;
};
// 所以我们一般都建议这样
class Date
{
public:
Date(int year)
{
_year = year;
}
private:
int _year;
};
// 或者这样。
class Date
{
public:
Date(int year)
{
m_year = year;
}
private:
int m_year;
};
// 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。


其实总结一下就是容易区分变量名,不容易搞混。

日期类中的构造函数:

1
2
3
//Date.h
Date(int year = 1, int month = 1, int day = 1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 全缺省的构造函数定义 Date.cpp
Date::Date(int year , int month ,int day )
{
if (year >= 1 &&
month <= 12 && month >= 1 &&
day >= 1 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}

注意 当函数的声明和定义分离时,缺省值在声明给,定义不用给!!!

析构函数

概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

特性

析构函数是特殊的成员函数。 其特征如下:

​ 1.析构函数名是在类名前加上字符 ~。

​ 2.无参数无返回值。

​ 3.一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

​ 4.对象生命周期结束时,C++编译系统系统自动调用析构函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);

_size = 0;
_capacity = capacity;
}

~SeqList()
{
if (_pData)
{
free(_pData ); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}

private :
int* _pData ;
size_t _size;
size_t _capacity;
};

​ 5.关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认 析构函数, 对会自定类型成员调用它的析构函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}

拷贝构造函数

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
1
2
3
4
// 拷贝构造函数Date.h
// d2(d1)
Date(const Date& d);

1
2
3
4
5
6
7
 Date::Date(const Date& d)//Date.cpp
{
_year = d._year;
_month = d._month;
_day = d._day;
}

	3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷			贝我们叫做浅拷贝,或者值拷贝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
// 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
Date d2(d1);
return 0;
}


​ 4.那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像 日期类这样 的类是没必要的。那么下面的类呢?验证一下试试?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//这里会发现下面的程序会崩溃掉,这里就需要深拷贝去解决。
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
}

操作符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意: 不能通过连接其他符号来创建新的操作符:比如operator@

  	重载操作符必须有一个类类型或者枚举类型的操作数

​ 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义

​ 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定 为第一个形参 .

​ .*、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

+=重载

改变自己的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Date& operator+=(int day);//声明

Date& Date:: operator+=(int day)//定义
{
if (day < 0)//小于零相当于减正数 >0好算
{
return (*this) -= (-day);
}

_day += day;//先加一下

while (_day>GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);;//看这个月的天数能不能抵消
++_month;
if (_month ==13)
{
_year++;
_month = 1;
}
}
return *this;
}

+操作符重载

不改变自己的值

写+= 然后+复用+= 因为+=是引用返回,效率能高一点

1
2
3
4
5
6
7
8
9
Date operator+(int day)const;//声明

Date Date:: operator+(int day)const//定义
{
Date d(*this);//因为不改变自身的值所以复制一份,然后返回复制的拿份的值(因为函数结束变量会销毁)
d += day;
return d;
}

-=重载

改变自己的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Date& operator-=(int day);//声明

Date& Date:: operator-=(int day)//定义
{
if (day < 0)
{
return (*this) +=(-day);
}
_day -= day;//先减一下
while (_day<GetMonthDay(_year,_month))
{
--_month;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return*this;
}

-操作符重载

不改变自己的值

1
2
3
4
5
6
7
8
9
Date operator-(int day)const;//声明

Date Date:: operator-(int day)const
{
Date d(*this);
d -= day;
return d;
}

前置++重载

先加再用

1
2
3
4
5
6
Date& Date:: operator++()
{
*this += 1;//调用一个+=函数
return *this;
}

后置++重载

先用再加

1
2
3
4
5
6
7
8
// 后置++ 先用再+  传一个int变量用来区分前置后置 有 int 就是后置
Date Date:: operator++(int)//先把自己的值拷贝给tmp,然后返回tmp,用tmp,。
{
Date tmp(*this);
*this += 1;
return tmp;
}

前置 - -重载

先减再用

1
2
3
4
5
6
Date& Date:: operator--()
{
*this -= 1;
return *this;
}

后置- - 重载

先用再减

1
2
3
4
5
6
7
Date Date:: operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}

比较运算符重载只需要写一个等于和一个小于(大于和等于也可以),其余的复用就行了,看我代码就可以理解

>重载

1
2
3
4
5
bool operator>(const Date& d)const
{
return !(*this <= d);
}

<重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool Date:: operator < (const Date& d)const
{
if ((_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day))
{
return true;
}
else
{
return false;
}
}

>=重载

1
2
3
4
5
inline bool operator >= (const Date& d)const
{
return !(*this < d);
}

<=重载

1
2
3
4
5
bool operator <= (const Date& d)const
{
return ((*this < d) || (*this == d));
}

==重载

1
2
3
4
5
6
7
bool Date:: operator==(const Date& d) const
{
return ((_year == d._year) &&
(_month == d._month) &&
(_day == d._day));
}

!=重载

1
2
3
4
5
bool operator != (const Date& d)const
{
return !(*this == d);
}

赋值运算符 = 重载

1
2
3
4
5
6
7
8
9
10
11
Date& Date::operator=(const Date& d)
{
if (this != &d)//防止自己给自己赋值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}

const成员函数

在不改变类成员变量的情况下我们一般给类的成员函数加const

可由于this指针是隐藏的,所以我们在函数名后面加const

就像这样:void Print()const;

日期类我给能加const的函数都加了

取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如不想让别人获取到指定的内容!

1
2
3
4
5
6
7
8
9
10
11
12
Date* Date:: operator &()//一般像这种短的函数写在类里当内联函数处理
{
//return this;
return nullptr;
}

const Date* Date:: operator &()const//如果是const变量就调用这个函数
{
//return this;
return nullptr;
}

日期类的实现

Date.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#pragma once
#include<iostream>
using namespace std;

class Date
{
public:
// 获取某年某月的天数√
int GetMonthDay(int year, int month);
// 全缺省的构造函数√
Date(int year = 1, int month = 1, int day = 1);
void Print()const;//打印√
// 拷贝构造函数√
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)√
Date& operator=(const Date& d);
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day)const;

// 日期-天数
Date operator-(int day)const;
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
//>运算符重载
bool operator>(const Date& d)const
{
return !(*this <= d);
}

// ==运算符重载√
bool operator==(const Date& d) const;

// >=运算符重载√
inline bool operator >= (const Date& d)const
{
return !(*this < d);
}

// <运算符重载√
bool operator < (const Date& d)const;

// <=运算符重载√
bool operator <= (const Date& d)const
{
return ((*this < d) || (*this == d));
}

// !=运算符重载√
bool operator != (const Date& d)const
{
return !(*this == d);
}
// 日期-日期 返回天数
int operator-(const Date& d)const;
private:
int _year;
int _month;
int _day;
};


Date.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#define _CRT_SECURE_NO_WARNINGS 1
#include"date.h"

// 获取某年某月的天数
int Date:: GetMonthDay(int year, int month)
{
static int Mday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//定义成静态的避免多次开辟空间
if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && (month == 2))
{
return 29;
}
else
{
return Mday[month];
}
}

// 全缺省的构造函数
Date::Date(int year , int month ,int day )
{
if (year >= 1 &&
month <= 12 && month >= 1 &&
day >= 1 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
void Date::Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}

// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}

// 日期+=天数 写+= 然后+调用+= 因为+=是引用返回
Date& Date:: operator+=(int day)
{
if (day < 0)//小于零相当于减正数 >0好算
{
return (*this) -= (-day);
}

_day += day;//先加一下

while (_day>GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);;//看这个月的天数能不能抵消
++_month;
if (_month ==13)
{
_year++;
_month = 1;
}
}
return *this;
}

// 日期+天数
Date Date:: operator+(int day)const
{
Date d(*this);
d += day;
return d;
}

// 日期-天数 这其实是就是把不合理的数据变成合理的数据
Date Date:: operator-(int day)const
{
Date d(*this);
d -= day;
return d;
}

// 日期-=天数
Date& Date:: operator-=(int day)
{
if (day < 0)
{
return (*this) +=(-day);
}
_day -= day;//先减一下
while (_day<GetMonthDay(_year,_month))
{
--_month;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return*this;
}

// 前置++
Date& Date:: operator++()
{
*this += 1;//调用一个函数
return *this;
}

// 后置++ 先用再+
Date Date:: operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}

// 后置--
Date Date:: operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}

// 前置--
Date& Date:: operator--()
{
*this -= 1;
return *this;
}

// ==运算符重载
bool Date:: operator==(const Date& d) const
{
return ((_year == d._year) &&
(_month == d._month) &&
(_day == d._day));
}

// <运算符重载
bool Date:: operator < (const Date& d)const
{
if ((_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day))
{
return true;
}
else
{
return false;
}
}

// 日期-日期 返回天数
int Date:: operator-(const Date& d)const
{
int flag = 1;//默认*this大
int count = 0;
Date max = *this;
Date min = d;
if ((*this)< d)
{
flag = -1;//*this小的话 剪完之后就是负数所以flag=-1
min = *this;
max = d;
}
while (min!=max)//min+几次 count就+几次
{
min++;
count++;
}
return count * flag;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


类和对象中,通过日期类来深入理解构造函数,析构函数,重载,拷贝构造,赋值重载,取地址重载,操作符重载
https://6jackjiang.github.io/2022/05/24/categories/c++/类和对象中,通过日期类来深入理解构造函数,析构函数,重载,拷贝构造,赋值重载,取地址重载,操作符重载/
作者
米兰的小铁匠
发布于
2022年5月24日
许可协议