C++ -3- 类和对象 (中) | 拷贝构造函数 赋值运算符重载(二)
4.拷贝构造函数
什么是拷贝构造函数?
- 示例:
class Date
{
public://构造函数Date(int year=2023,int month=2,int day=27){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date d){……}private:int _year;int _month;int _day;
};void TestDate1(){Date d1(2023, 4, 1);//调用构造函数Date d2(d1);//调用拷贝构造函数//orDate d2 = d1;//调用拷贝构造函数
}
拷贝初始化构造函数的作用是将一个已知对象的数据成员值拷贝给正在创建的另一个同类的对象
- 无穷递归?
Date(Date d){……}
-
首先,分析传值传参的过程
-
传引用传参:
没有拷贝
的过程,直接传 -
传值传参:
内置类型编译器可以直接拷贝(浅拷贝/值拷贝——一个字节一个字节的拷贝)
自定义类型编译器无法拷贝,需要调用 拷贝构造函数(浅拷贝 or 深拷贝) -
为什么会无穷递归:
-
∴ 正确的拷贝构造: 传引用传参
//拷贝构造函数
Date(const Date& d)
{ //……
}
应用——示例:日期计算器
日期计算器:实现一个函数,获取 X 天后的日期
第一种:
int GetMonthDay(int year, int month)//获取对应年月的天数
{if (month > 0 && month < 13){int array[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2){if ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))){++array[2];}}return array[month];}else{cout << "month error" << endl;return 0;}
}class Date
{
public://构造函数Date(int year = 2023, int month = 2, int day = 27){_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;}//获取 X 天后的日期Date& GetAfterXDate1(int x){_day = _day + x;int monthDay = GetMonthDay(_year, _month);while (_day > monthDay){_day -= monthDay;++_month;if (_month > 12){++_year;_month = 1;}monthDay = GetMonthDay(_year, _month);}return *this;//调用拷贝构造}private:int _year;int _month;int _day;
};void TestDate1()
{Date d1(2023, 4, 1);d1.GetAfterXDate1(10000);d1.Print();
}
第二种:不改变原日期
//获取 X 天后的日期
Date GetAfterXDate2(int x)
{Date tmp = *this;//调用拷贝构造tmp._day = _day + x;int monthDay = GetMonthDay(tmp._year, tmp._month);while (tmp._day > monthDay){tmp._day -= monthDay;++tmp._month;if (tmp._month > 12){++tmp._year;tmp._month = 1;}monthDay = GetMonthDay(tmp._year, tmp._month);}return tmp;//调用拷贝构造
}
void TestDate2()
{Date d1(2023, 4, 1);Date d2 = d1.GetAfterXDate2(10000);
}
- 拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做
浅拷贝
,或者值拷贝
。
什么情况下需要自己实现拷贝构造函数?
-
编译器默认的拷贝构造函数 能完成“值拷贝”(浅拷贝)
所以,日期类(都是内置类型)没有自己实现拷贝构造函数的必要 -
什么情况下需要自己实现拷贝构造函数?
-
sum. 自己实现了析构函数释放空间(意味着设计资源管理),则需要自己实现拷贝构造函数
5.赋值运算符重载
运算符重载(重要)
运算符重载:让【自定义类型】可以使用运算符
例如: 内置类型可以直接比大小;自定义类型不可以直接比大小
//内置类型 int a = 1,b = 2; a==b; a>b;//自定义类型 class Date { //…… } Date d1;Date d2; d1==d2;? d1<d2;?
运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator
后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
- 将运算符重载函数放在全局:
bool operator==(const Date& d1,const Date& d1)
{return d1._day == d2._day&& d1._month == d2._month&& d1._year == d2._year;
}
- 将运算符重载函数放入类:
- 这里需要注意的是,左操作数是
this
,指向调用函数的对象 - 操作符使用时包含几个操作数,运算符重载函数就含有几个参数
- 这里需要注意的是,左操作数是
bool operator==(Date* this, const Date& d2)
!这里需要注意的是,左操作数是this,指向调用函数的对象
----------------------------------------------
// ==运算符重载
bool Date::operator==(const Date& d)
{return _day == d._day&& _month == d._month&& _year == d._year;
}
// >运算符重载
bool Date::operator>(const Date& d)
{if (_year < d._year)return false;if (_year > d._year)return true;if (_month < d._month)return false;if (_month > d._month)return true;if (_day < d._day)return false;if (_day > d._day)return true;return false;
}
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个 类类型 参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- ⭐
.*
/::
/sizeof
/?:
/.
注意以上5个运算符不能重载。
赋值运算符重载
1.赋值运算符重载的特性
赋值运算符 “=”
,可以连续赋值,例如:
int i = 1,j = 0,k = 0;
j = k = i;//连续赋值
∴ 赋值运算符重载函数应该有 返回值
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{if (*this == d)//如果时自己给自己赋值就不用进行下列操作了return *this;_day = d._day;_month = d._month;_year = d._year;return *this;
}
- 赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
拷贝构造函数和赋值重载函数
默认生成的拷贝函数/赋值重载函数:
- 内置类型浅拷贝
- 自定义类型,调用这个成员的拷贝构造函数/赋值重载函数
对比↕
默认生成的构造函数/析构函数:
- 内置类型不处理
- 自定义类型,调用它的默认构造函数
END