上次介绍了构造函数和析构函数:C++初阶类与对象(二):详解构造函数和析构函数
今天就来接着介绍新的内容:
1.拷贝构造函数
1.拷贝构造函数概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
2.拷贝构造函数特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
特性2:拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
特性3:若未显式定义,编译器会生成默认的拷贝构造函数
我们在date类中并没有写拷贝构造函数,但是date类中确进行了拷贝,因为生成默认的拷贝构造函数
在date类拷贝的时候,生成默认的拷贝构造函数,将内置类型对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
但是拷贝自定义类型时,date类也会生成默认的拷贝构造函数,不过会调用自定义类型的拷贝构造函数
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
从图中,我们可以看出st1和st2中_a指向的空间相同
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
从图中可以看出,我们自己写拷贝构造函数时,st1和st2的_a指向不同空间
拷贝构造函数典型调用场景:
1.使用已存在对象创建新对象
2.函数参数类型为类类型对象
3.函数返回值类型为类类型对象
2.赋值运算符重载
1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.重载双操作数的运算符,第一个参数是左操作数,第二个参数是右操作数
2.不能通过连接其他符号来创建新的操作符:比如operator@
3.重载操作符必须有一个类类型参数
4.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
5.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
6. .* :: sizeof ?: . 注意以上5个运算符不能重载。笔试会有
1.将operator写在类外面
1.将operator写在类里面
2.赋值运算符重载
2.赋值运算符重载格式
1.参数类型:const T&,传递引用可以提高传参效率
2.返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回*this :要复合连续赋值的含义
class Date
{
public:
//构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
cout << "拷贝构造函数" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//这里如果没有"&"引用运算符的话,那他的返回值就是拷贝值,就会调用拷贝构造函数
Date& operator=(const Date& d)
{
//this是d2的地址
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//返回d2的值
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 8, 20);
//这两种所表达的意思是相同的,其底层的汇编也是相同的
Date d2;
d2 = d1;//Date d2(d1);
return 0;
}
2赋值运算符的使用
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。
此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。
注意:运算符是指"+"-"*"/"之类的,赋值运算符是指"=","<=",">=="之类,运算符可以重载成全局函数
说白了,赋值运算符不能写在类外
2.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值
3.赋值运算符重载与拷贝构造调用区别:
int main()
{
Date d1;//调用构造函数
Date d2 = d1;//这是调用拷贝构造;
Date d2(d1);//或者这样,和上一行代码是一样的
//一个已经存在的对象,拷贝初始化另一个要创建的对象时拷贝构造
Date d3;
d3 = d1;//这是赋值重载函数:两个对象都存在,进行赋值拷贝
return 0;
}
3.const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
加了const的类似也被称为常变量类型
const成员函数主要是给对象常量来使用(const Date d1;),因为:
如果const对象调用非const成员函数有可能会扩大权限;非const对象调用const成员函数可以,只会缩小权限
bool Date::operator < (const Date& d)
{
return !((*this) >= d);
}
int main()
{
const Date d1(2024, 1, 1);
Date d2(2024,15);
d2 < d1;
d1 < d2;
return 0;
}
看似d2 < d1; d1 < d2;
两句都没错,但实际上欠着正确,后者错误:
结论:
能定义成const的成员函数都应该定义成const,这样const对象和非const对象都可以调用
要修改成员变量的成员函数,不能定义成const,这样const对象不能调用 (会报错) 非const才能调用
4.赋值运算符重载日期的例子
Date.h:
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
//友元函数声明
//自定义类型流插入
friend ostream& operator<<(ostream& out, const Date& d);
//自定义类型流提取
friend istream& operator>>(istream& in, Date& d);
public:
//构造函数,日期初始化
Date(int year = 2023, int month = 8, int day = 20);
//拷贝构造函数
Date(const Date& d);
//获取某年某月的天数
int GetMonthDay(int year,int month);
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
//日期-日期=天数
int operator-(const Date& d);
//打印
void printf();
//赋值运算符“=”
Date& operator=(const Date& d);
//赋值运算符“>”
bool operator>(const Date& d);
//赋值运算符“<”
bool operator<(const Date& d);
//赋值运算符“==”
bool operator==(const Date& d);
//赋值运算符“<=”
bool operator<=(const Date& d);
//赋值运算符“>=”
bool operator>=(const Date& d);
//赋值运算符“!=”
bool operator!=(const Date& d);
//赋值运算符,前置“++”
Date& operator++();
//赋值运算符,后置“++”
Date operator++(int);//因为tmp是临时对象,因此只能以值的方式返回,不能返回引用
//赋值运算符,前置“--”
Date& operator--();
//赋值运算符,后置“--”
Date operator--(int);//因为tmp是临时对象,因此只能以值的方式返回,不能返回引用
private:
int _year;
int _month;
int _day;
};
Date.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//构造函数,日期初始化
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//获取某年某月的天数
int Date::GetMonthDay(int year,int month)
{
//这里将数组变为,全局变量是为了提高运行效率
//因为这个函数会经常调用
static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = arr[month];
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
day += 1;
}
return day;
}
// 日期+=天数
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
//定义tmp,是因为了不改变原来的值
Date tmp = *this;
tmp += day;
return tmp;
}
// 日期-天数
Date Date::operator-(int day)//“+”或“-”不用引用
{
while (day > _day)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
day -= GetMonthDay(_year, _month);
}
_day -= day;
return *this;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
_day -= day;
while (_day < 1)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//日期-日期=天数
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int symbol = 1;//代表符号
if (*this < d)
{
max = d;
min = *this;
symbol = -1;
}
int day = 0;//两个日期相差天数
while (max!=min)
{
++min;//一天一天的加
++day;
}
return day*symbol;
}
//打印
void Date::printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//赋值运算符“=”
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
//赋值运算符“>”
bool Date::operator>(const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year && _month > d._month)
return true;
else if (_month == d._month && _day > d._day)
return true;
return false;
}
//赋值运算符“<”
bool Date::operator<(const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_month == d._month && _day < d._day)
return true;
return false;
}
//赋值运算符“==”
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//赋值运算符“<=”
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
//赋值运算符“>=”
bool Date::operator>=(const Date& d)
{
return *this > d || *this == d;
}
// 赋值运算符“ != ”
bool Date::operator!=(const Date & d)
{
return !(*this == d);
}
//赋值运算符,前置“++”
Date& Date::operator++()
{
*this += 1;
return *this;
}
//赋值运算符,后置“++”
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;//因为tmp是临时对象,因此只能以值的方式返回,不能返回引用
}
//赋值运算符,前置“--”
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//赋值运算符,后置“--”
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;//因为tmp是临时对象,因此只能以值的方式返回,不能返回引用
}
//自定义类型流插入,ostream是流插入类型
//他要定义为全局,因为他要确保out是在左边
//如果将他定义在类中ostream& operator<<(const Date& d),左边就变成了this
//祖师爷规定在类中左边必须是this
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//自定义类型流提取
istream& operator>>(istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month < 13
&& day > 0 && day <= d.GetMonthDay(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
return in;
}
text.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
int main()
{
Date d1(2003,12,1);
Date d2(2003,12,5);
Date d;
cin >> d1;
cout << d1 << endl;
cin >> d;
cout << d << endl;
return 0;
}