目录
默认成员函数
今天学习了类中的默认成员函数,主要有六个:构造函数,拷贝构造函数,析构函数,运算符重载和取地址重载(普通对象和const对象取地址)。
我使用了日期类对这一块的内容进行了加深巩固。
-
实现构造函数:
- 构造函数是与类名相同的、无参的(不需要传参)一个函数,相当于写链表时实现的Init()
- 构造函数
- 不太准确地来说,构造函数有三个:
1. 全缺省
2. 自己实现的、无参数的构造函数
3. 我们不实现,编译器自动生成的默认构造函数
构造函数不能显示调用:
Date d1.Date();
因为编译器自动生成的构造函数对于自定义类型不做处理,所以需要我们自己实现。
但是编译器自动生成的构造函数可以对自定义类型调用其对应的默认构造函数,所以我们的类中如果存在自定义类型,我们要实现的应该是那个自定义类型中的默认构造函数。
// 默认构造函数,全缺省
Date::Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
-
实现拷贝构造函数:
1. 拷贝构造函数也是一种特殊的构造函数,是构造函数的重载函数。(与类同名但是参数不同,是引用)
2. 拷贝构造函数是对于一个即将实例化的对象和一个已经存在的对象进行拷贝。
3. 编译器自动生成的默认拷贝构造函数会对内置类型进行按字节序的浅拷贝,对自定义类型会进行调用其对应的拷贝构造函数,同样地,们的类中如果存在自定义类型,我们要实现的应该是那个自定义类型中的默认拷贝构造函数。
4. 由于存在浅拷贝的问题,如果有指针,那么会造成野指针,为什么呢?
因为自动实现的是按字节序进行的拷贝,这就会让两个指针的内容完全一样,也就是都指向了同一个地方,一个指针改变其指向的内容,另一个指针却还指向原来的内容,就会造成野指针问题。
从d1拷贝到d2:
// 默认拷贝构造函数
Date::Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Test1()
{
Date d1(2023, 1, 7);
Date d2(d1); // 拷贝构造
}
-
实现析构函数
1. 析构函数实现的是和链表阶段Destory()相似的功能,也即资源的清理
2. 析构函数是类名前加上“~”
3. 析构函数与构造函数相同,对于自定义类型会去调用它对应的析构函数,而内置类型不做处理,他们会随程序的消亡而消亡。
由于日期类均为内置类型,没必要实现析构函数,在此我重新构建了一个“A”类来学习析构函数。
class A
{
public:
// 因为A的_a是内置类型所以要自己实现默认构造函数
A(int a = 0, int capacity = 5)
{
_a = a;
_array = (int*)malloc(sizeof(int) * capacity);
}
// 拷贝构造函数
A(A& a)
{
_a = a._a;
}
// 析构函数
~A()
{
free(_array);
_array = nullptr;
_a = 0;
}
private:
int _a;
int* _array;
};
-
赋值重载函数
1. 赋值重载与拷贝构造函数不同之处在于:
赋值重载函数是对于两个已经存在的对象来说的,而拷贝构造函数是对于一个即将创建实例化的对象和一个已经存在的对象之间的拷贝。
// 测试赋值重载
void Test1()
{
Date d1(2023, 1, 7);
Date d2 = d1;// 拷贝构造->对自定义类型会浅拷贝,不需要自己实现
Date d3(d1); // 拷贝构造
Date d4;
d4 = d1;// 赋值重载
}
2. 赋值重载函数参数需要传递引用,如果是传值,就会引发拷贝构造,因为中间产生了临时变量,这个临时变量想要得到传过来的值就需要调用拷贝构造函数,而这个过程又是一个传值的过程,又会调用拷贝构造函数,从而会导致无穷递归。
3. 赋值重载函数需要返回*this。
4. 如果我们没有实现赋值重载函数,编译器会自动实现一个,但是与拷贝构造函数一样,只能实现浅拷贝。
5. 我们需要优化的点还有一点是不能让自己和自己进行赋值。
这里使用的是地址之间的比较:地址一样,那就说明是自己在和自己进行拷贝。
用本体进行比较也可以,但是效率低,并且不能够用等号进行判等,我们正在实现的就是等号的重载,所以我们需要实现一个“!=” 的重载才能完成。
6. 如果不返回引用,也可以,但是会进行拷贝构造的调用,降低效率。
为什么能够返回引用呢?
因为需要的返回值在函数调用结束后仍然存在。
// 返回引用
Date& Date::operator=(Date& d)
{
// 用地址判断是否是和自己进行赋值
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
else
{
// 返回后,*this仍然存在
// *this就是函数外即将创建实例化的那个对象,所以可以引用返回,减少拷贝
return *this;
}
}
调用:
// 测试赋值重载
void Test1()
{
Date d2;
d2 = d1;// 赋值重载
}
-
"+="的重载
就像进制加法运算一样,满进制就进位。
day:满GetMonthDay()的改约天数就要往月份进位
month:满12到13往年份进位
year:无限大,可以一直在此位一直加
// +=重载
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
// 加到12月还没加完,加年份
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
-
“++”的重载
为了进行区分前置++与后置++,引入了占位符参数。
他们写出来都是:operator++();
所以进行了更改:
前置++:operator++();
后置++:operator++(int);// 这里只能用int,因为我们是不需要传参的,这个传参的动作由编译器自己完成,而编译器自己传的是int。
// 前置++重载
// 返回后*this存在,返回的是引用
Date& Date::operator++()
{
// 加一天
*this += 1;
return *this;
}
// 后置++重载
// 因为返回的是++之前的值,保存在了局部变量ret中
// 返回后ret不存在,所以返回的是值
Date Date::operator++(int)
{
// 调用的是拷贝构造ZQ
//Date ret = *this;
Date ret(*this);
// 也要加一天
*this += 1;
return ret;
}
-
“--”重载
与“++”类似
// 前置--重载
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--重载
Date Date::operator--(int)
{
Date ret = *this;
*this -= 1;
return ret;
}