当我们创建了一个对象时,想让它的内容和一个已经创建好的对象的内容相同,那么就必须用到拷贝构造。拷贝构造编译器也会自动生成,也是C++类中的6个默认函数之一。
拷贝构造函数格式类名(const 类名& 变量名)
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date date(2020, 2, 3);
//调用拷贝构造
Date d1(date);
date.Print();
d1.Print();
return 0;
}
当我们自己写拷贝函数时,需要注意拷贝函数的参数,必须为引用,如果不使用引用会造成无穷递归。我们知道,调用函数传参时,会把实际参数传给函数,函数会创建一个临时变量,然后将实际参数里的内容拷贝到临时变量中,这里面存在拷贝操作。而我们自定义类型的拷贝规则是我们自己写的,当然也可以用编译器自动生成的。拷贝构造函数如果不用引用当函数参数,会存在拷贝操作,会调用临时变量的拷贝构造,而拷贝构造又要接收函数传值,无穷拷贝。
而我们用引用就不会发生拷贝操作,函数里的形参是主体的一个别名。不需要拷贝操作。就不会造成无穷递归。
编译器自动生成的拷贝构造是浅拷贝,浅拷贝就是**按字节拷贝,只拷贝内容不拷贝资源。**当我们一个对象是开辟了内存的对象。例如我们的栈,是存在内存利用的,有指针指向该内存。当我们利用拷贝构造拷贝时,成员变量的内容是一样的,指针的指向也是一样的,这时候同时指向一块内存空间。当一个对象生命周期结束调用析构函数,会释放该空间,另一个拷贝了的对象声生命周期结束也会自动调用析构函数,此时同一个内存空间被释放两次,程序就会崩溃。
C++中为了增强代码的可读性,引入了运算符的重载。运算符重载和普通的成员函数类似,但是函数名必须为operator+需要重载的运算符
例如重载==。
bool operator==(const Date& d1, const Date& d2)
{}
引入了运算符重载函数,当我们操作我们自定义的类型时,可以和内置类型一样操作。例如a和b是int类型,可以 if (a == b)
,引入运算符重载后,自定义类型对象d1和d2可以这么写if (d1 == d2)
判断两个自定义类型的值是否相等。
但是并不是有了运算符重载,我们就可以为所欲为了。以下几点需要注意:
1、内置类型不能使用的运算符,自定义类型也不可以重载。例如operator#
2、运算符中有5个自定义类型不能重载,.*
,::
,sizeof
,?:
,.
3、我们不能重载内置类型的运算符。
4、重载操作符必须有一个类类型或者枚举类型的操作数。
5、为了保证类的封装性,我们的运算符重载一般写在类里面,当成成员函数。一旦当成成员函数,该函数的第一个形参肯定是隐藏的this指针(非静态成员函数)。所以形参看起来应该比操作数少1。
#include
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//运算符重载函数
//=>bool operator==(Date* this, const Date& d)
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 2, 4);
Date d2(2021, 2, 3);
Date d3(2021, 2, 3);
//d1.operator==(d2) 简写:d1 == d2
cout << (d1 == d2) << endl;
cout <<(d1 == d3) << endl;
cout << (d2 == d3) << endl;
return 0;
}
运算符重载后,编译器就知道如何判断两个对象是否相等了。
运行结果:
了解了运算符重载,现在我们来看赋值运算符重载,操作方法类似,函数名必须为operator+需要重载的赋值运算符
返回值类型和参数自己决定。赋值运算符的重载是为了让自定义类型可以像内置类型一样去使用赋值运算符
像int类型的a,可以完成 +=
,前置++
,后置++
等等,我们自定义类型编译器不知道如何相加,我们就必须重载,让编译器按照我们所写的规则去运算操作。但是这个是默认的成员函数,即使我们不写,编译器也会自动生成。(浅拷贝–按字节拷贝)
函数样式:
未优化:
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
优化后:
//返回值是对象,返回对象的引用可以提高效率,最好是返回引用
//目的是可以像内置类型一样进行连续赋值
//例如int a; a = b = c 运算顺序是从右往左
//if语句是判断两个对象是否是同一个对象,同一个对象就不需要进行操作
Date& operator=(const Date& d)
{
if (this != &d) //通过对象地址判断
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
测试:
#include
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//赋值运算符重载=
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 2, 4);
Date d2(2021, 2, 3);
d1 = d2;
//打印d1和d2的日期
d1.Print();
d2.Print();
return 0;
}
运行结果:
拷贝构造 和 =赋值运算符重载的区别?
拷贝构造是一个对象不存在,要实例化它,用另一个已经存在的 对象去实例化 Date d2(d1)
赋值重载是两个对象都已经被初始化了,已经给存在的,是把一个对象的值拷贝到另一个对象中 d2 = d1
如果类中没有资源需要释放,这两个函数都可以不用写
实现一个完整的日期类:
#include
using namespace std;
class Date
{
public:
//获得当前年月的天数
int GetDay(int year, int month)
{
static int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
//判断当年是否为闰年
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
{
++day;
}
return day;
}
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
//判断日期的有效性
if (year <= 0 || month <= 0 || month > 12
|| day <= 0 || day > GetDay(year, month))
{
_year = 1;
_month = 1;
_day = 1;
cout << "输入日期有误,恢复默认日期 1-1-1" << endl;
}
else
{
_year = year;
_month = month;
_day = day;
}
}
//判断两个日期是否相等
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
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;
}
bool operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool operator<(const Date& d)
{
return !(*this >= d);
}
bool operator<=(const Date& d)
{
return !(*this > d);
}
//赋值运算符
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//使日期加上天数
Date& operator+=(int day)
{
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetDay(_year, _month))
{
_day -= GetDay(_year, _month);
++_month;
//年份进位
if (_month == 13)
{
++_year;
_month = 1;
}
}
}
return *this;
}
//日期加上某天数后的日期
Date operator+(int day)
{
Date tmp = *this;
return tmp += day;
}
//前置++
Date& operator++()
{
return *this += 1;
}
//后置++
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
Date& operator-=(int day)
{
if (day < 0)
{
*this += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
--_month;
//年份进位
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetDay(_year, _month);
}
}
return *this;
}
Date operator-(int day)
{
Date tmp = *this;
return tmp -= day;
}
Date& operator--()
{
return *this -= 1;
}
Date operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
//日期-日期 = 天数
int operator-(const Date& d)
{
Date min = d;
Date max = *this;
int flag = 1;
if (min > max)
{
Date tmp = min;
min = max;
max = tmp;
flag = -1;
}
int res = 0;
while (min != max)
{
++min;
++res;
}
return flag * res;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
构造函数 与 析构函数