一、本章重点
- 六个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造
- 赋值运算符重载
- const修饰成员函数
- 实现一个日期类
- 取地址及const取地址运算符重载
二、六个默认成员函数
我们写的每个类,编译器都会自动生成6个默认的成员函数。
分别是:
- 默认构造函数
- 默认析构函数
- 拷贝构造(浅拷贝/值拷贝)
- 赋值运算符重载
- 取地址运算符重载(一般不用)
- 加了const的取地址运算符重载(一般不用)
本篇文章,我们将分别介绍这6个默认的成员函数。
三、默认构造函数
首先介绍一下,什么是构造函数?
构造函数就是一个特殊的成员函数,在创建对象时,编译器会自动调用,完成对象初始化工作。
构造函数的特性:
- 没有返回值。
- 函数名与类名相同
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
?构造函数可以重载
#include<iostream>
using namespace std;
class Date
{
public:
Date()//无参构造
{
cout << "Date" << endl;
}
Date(int year, int month, int day)//有参构造
{
_year = year;
_month = month;
_day = day;
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用无参构造
Date d2(2022, 5, 2);//调用有参构造
return 0;
}
?什么是默认构造函数?
默认构造函数指的是编译器自动生成的构造函数?
这句话不是很准确,默认构造函数一般是指我们在实例化对象不用传参就可自动调用的构造函数。
一般有3个
- 编译器自动生成的
- 我们自己写的无参的
- 我们自己写的全缺省的
我们先来看看编译器自动生成的默认构造函数做了什么?
#include<iostream>
using namespace std;
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用编译器自动生成的默认构造函数
d1.Print();
return 0;
}
我们发现打印的都是随机值,那么编译器自动生成的默认构造函数好像什么事情都没做。
但真的什么都没做吗?实际上这个自动生成的默认构造函数还是有做事情的
我们在日期类中加上一个A类型的对象。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
};
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
A a;
};
int main()
{
Date d1;//调用编译器自动生成的默认构造函数
d1.Print();
return 0;
}
?我们发现,编译器自动生成的默认构造函数调用了A类的默认构造函数。
总结:
编译器自动生成的默认构造函数对内置类型(基本类型)不处理,对自定义类型会调用它的默认构造函数。
如果A类没有默认构造函数呢?
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
{
cout << "A(a)" << endl;
}
};
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
A a;
};
int main()
{
Date d1;//调用编译器自动生成的默认构造函数
d1.Print();
return 0;
}
?先实例化d1,调用编译器自动生成的日期类的默认构造函数,它再去调用A类的默认构造函数,但A类没有默认构造函数,只有一个有参构造(需要传参才能调用,不是默认构造函数),因此才会报错。
如果将A类的有参构造改为全缺省、无参、或者使用编译器生成的默认构造函数都可以解决。
需要说明一点的是,默认构造函数只能有一个,否则编译器不知道该调用那个。
同时,只要你重载了构造函数,编译器便不会自动生成默认构造函数。
还有就是不要这样去实例化对象。
四、默认析构函数
析构函数的概念:
与构造函数一样,是一个特殊的成员函数。
在对象销毁时会自动调用析构函数,主要完成对资源的清理。
特性:
- 函数名:在类名前面加个~
- 没有返回值
- 没有参数
- 一个类只能有一个析构函数
- 在对象生命周期结束自动调用
默认析构函数,就是我们不写编译器自动生成的析构函数。
与编译器自动生成的默认构造函数一样,默认析构函数对内置类型不处理,对自定义类型(class、struct自定义的类型)会调用它们的默认析构函数。
在与堆区有关的类需要自己写一个析构函数,完成堆区的释放,不然会内存泄露。
五、拷贝构造
拷贝构造:特殊的构造函数,用一个对象复制(拷贝)来初始化另一个对象。
关于拷贝构造的使用
?拷贝构造能这样写吗?
答:不能,否则引发无穷递归。
当你用d1去调用拷贝构造初始化d2时,需要传参,传参需要调用拷贝构造,调用拷贝构造需要传参,传参需要拷贝构造。。。。。会引发无穷的递归。
因此这里应该使用引用传参,而不是传值传参。
?顺便提一下,传地址虽然也可以完成拷贝,不过调用时需要传地址,但这用起来不方便,因此不建议这样。
?需要说明的是,我们不写拷贝构造,编译器会自动生成一个拷贝构造。
编译器自动生成的拷贝构造完成的浅拷贝或者说值拷贝
class Stack
{
public:
Stack(int capacity = 4)
{
if (capacity == 0)
{
_a = nullptr;
_capacity = _size = 0;
}
else
{
_a = (int*)malloc(sizeof(int) * capacity);
_size = 0;
_capacity = capacity;
}
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;
int _capacity;
int _size;
};
?
编译器自动生成的拷贝构造会完成浅拷贝或者说是值拷贝。
当类与堆区有联系的话,浅拷贝会带来堆区内存重复释放的问题,需要深拷贝解决。
六、赋值运算符重载
什么是运算符重载?
简单来说就是让运算符可以处理自定义类型。
赋值运算符重载,我们不写编译器会自动生成一个,完成的是值拷贝(浅拷贝)
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
Date(int year = 1, int month = 2, int day = 3)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2022, 5, 2);
d1 = d2;
return 0;
}
?赋值重载与拷贝构造的区别是:
拷贝构造是拿一个对象去复制初始化另一个对象。
而赋值重载是两个对象都存在,那一个对象去赋值给另一个对象。
一般的类,编译器生成的够用了。
但对于与堆区有联系的类,我们需要自己写一个赋值重载完成深拷贝。
七、实现一个日期类
class Date
{
public:
void Print()const
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
int GetMonthDay(int year, int month)
{
int m_arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = m_arr[month];
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
day++;
}
return day;
}
Date(int year = 1, int month = 2, int day = 3)
{
_year = year;
_month = month;
_day = day;
if (year < 0 || month <= 0 || month>12 || day <= 0 || day > GetMonthDay(year,month))
{
cout << "无效日期" << endl;
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
}
Date(const Date& d)//浅拷贝
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator+= (int day)
{
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
}
Date& operator-=(int day)
{
if (day < 0)
{
*this += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
}
return *this;
}
Date operator-(int day)const
{
Date temp = *this;
temp -= day;
return temp;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
int operator-(const Date& d)const
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int day = 0;
while (min != max)
{
min++;
day++;
}
return day * flag;
}
Date operator++(int)//后置++
{
Date temp = *this;
*this += 1;
return temp;
}
Date& operator++()//前置++
{
*this += 1;
return *this;
}
Date& operator--()//前置--
{
*this -= 1;
return *this;
}
Date operator--(int)//后置--
{
Date temp = *this;
*this -= 1;
return temp;
}
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);
}
bool operator>(const Date& d)const
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day > d._day)
{
return true;
}
}
}
return false;
}
bool operator>=(const Date& d)const
{
return (*this > d) || (*this) == d;
}
bool operator<(const Date& d)const
{
return !(*this >= d);
}
bool operator<=(const Date& d)const
{
return !(*this > d);
}
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
friend ostream& operator<<(ostream& out, Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, Date& d)
{
cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
八、加const的成员函数
加上const成员函数,这个const修饰的是this,如果不想修改this指向对象的内容可以加const修饰成员函数。
如下:
?那么_year和_month和_day都不能被修改了。
思考下面的问题:
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
答:本质上就是传地址给this指针的问题。
1.const对象传给不加const的this,权限放大,不能调用。
2.非const对象传给加const的this,权限缩小,能调用。
3.加了const的this传给不加const的this,权限放大,不能调用。
4.没加const的this传给加了const的this,权限缩小,能调用。
因此2,4能够调用,1和3不能调用。
九、取地址运算符重载(一般不用)
Date* operator&()
{
return this;
}
极少情况会重载这个取对象地址的运算符。
十、加const取地址运算符重载
const Date* operator&()const
{
return this;
}
与七相同,一般不用这个,加const取地址运算符是为了让加const对象能够取地址。
|