💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:C++初阶之路⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你学习C++
🔝🔝
类和对象
- 1. 前言
- 2. 拷贝构造函数
- 2.1 对拷贝构造函数参数的思考
- 2.2 默认拷贝构造函数
- 2.3 对拷贝构造函数的总结
- 3. 运算符重载
- 3.1 对运算符重载的思考
- 3.2 特殊的赋值运算符
- 3.3 前置++和后置++
- 3.4 运算符重载再理解
- 4. 总结以及拓展
1. 前言
本章重点:
本篇文章将详细讲解拷贝构造函数
和运算符重载,并介绍const成员的概念
拷贝构造函数和运算符重载
是类和对象中六大默认成员函数
的其中两个
拷贝构造类似于构造一个一模一样的你
2. 拷贝构造函数
我们在写代码的时候会遇见这种场景:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,7,30);
Date d2(d1);//用d1初始化d2
return 0;
}
使用一个已经存在的对象初始化
一个正在定义的对象就是在拷贝构造!
拷贝构造函数特征:
- 拷贝构造是构造函数的一个重载形式
- 拷贝构造函数只能有一个参数且必须
是类类型对象的引用!
比如Date类的拷贝构造:
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
2.1 对拷贝构造函数参数的思考
为什么拷贝构造函数的参数
一定要是:类类型对象的引用?
因为拷贝构造是用另一个对象初始化
所以是类类型对象可以理解
但是为啥必须传引用?
这是因为不传引用会引起无限递归!
可以这样理解无限递归:
假如加上了引用,调用拷贝构造时
传对象的别名进函数,就不会有
形参拷贝一份实参的过程,就不会死递归!
所以拷贝构造函数一定要加上引用!
2.2 默认拷贝构造函数
如果用户不显示写拷贝构造函数
编译器会自动生成一个默认构造函数
默认构造函数会对内置类型完成:
值拷贝(浅拷贝)
默认构造函数会去
调用自定义
类型的构造函数完成拷贝
对于内置类型来说,浅拷贝可能会有问题!
举个例子:
class Stack
{
public:
Stack(size_t capacity = 10)//构造函数
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const int& data)//插入函数
{
_array[_size] = data;
_size++;
}
~Stack()//析构函数
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
int *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1(10);
s1.push(1);
s1.push(2);
s3.push(3);
s3.push(4);
s3.push(5);
Stack s2(s1);
retrn 0;
}
stack类没有显示写拷贝构造函数
编译器会自动生成默认拷贝构造函数
默认构造函数是按照字节方式直接拷贝的
所以对象s1和s2的关系图如下:
array指向的空间是动态开辟的
是在堆区存储的,使用值拷贝的方式
s1和s2中的array指向的空间相同!
这么做会有啥问题?
当s1和s2生命周期结束时
会分别调用它们各自的析构函数
然而两个对象中的指针指向的空间相同
析构函数会调用两个free释放空间!
同一份空间释放两个就会出错!
2.3 对拷贝构造函数的总结
-
拷贝构造函数的参数必须是引用
-
若类中没有涉及到空间申请
则默认拷贝构造就够用了 -
下面这两种写法都是在拷贝构造:
Date d1(2023,7,30);
Date d2(d1);
Date d3 = d1;
拷贝构造的典型应用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
3. 运算符重载
在日期类中定义一个对象:
Date d1(2023,7,31);
假设现在想要计算100天
后是几月几号的话,直接使用+
无法实现这个功能!
Date d2 = d1+100; //普通的加号不能实现此功能
C++中,普通的加号只适用于内置类型
而自定义类型的加号种类太多
系统没有自行实现,需要用户自己写!
要自己实现的运算符需要运算符重载!
C++为了增强代码的可读性
引入了运算符重载
运算符重载是具有特殊函数名的函数
也具有其返回值类型,函数名字
以及参数列表
其返回值类型和参数列表与普通函数类似
使用关键字: operator
函数原型:
返回值类型 operator操作符(参数列表)
举例说明日期类+号的重载:
Date operator+(Date d1, int x);
3.1 对运算符重载的思考
运算符重载是针对自定义类型的
所以函数参数中必须有一个类类型参数
但是,如果运算符重载写在类外
它就不能访问类中的私有成员!
所以常常将运算符重载写在类中!
比如:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
写在类中时,类类型参数可以省略!
因为类中函数默认有this指针
指针this就代表了此类对象了!
3.2 特殊的赋值运算符
赋值运算符十分特殊
若我们不显示写,编译器会自动生成一个
所以赋值运算符只能写在类中
如果显示写在了类外的话
编译器会自动生成一个赋值函数
这就和你自己写的冲突了!
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
注:this是类对象的地址
*this就是类对象本身
赋值运算符返回类对象的原因是:
多次连续赋值
Date d1(2023,7,31);
Date d2;
Date d3;
d2 = d3 = d1;//连续赋值
拓展:
编译器自动生成的默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
总结:
如果类中未涉及到资源管理
赋值运算符是否实现都可以
一旦涉及到资源管理则必须要实现
3.3 前置++和后置++
对于++的运算符重载比较有争议
因为前置和后置是两种不同的函数!
C++为了区分前置和后置++
在后置++的函数中多加一个int
类型的参数来区别前置++
比如:
前置++:
Date& operator++();
后置++:
Date& operator++(int);
注意,虽然后置++多了一个参数
但是这个参数完全没用!
只用于区别前置++
++运算符的使用:
Date d1(2023,7,31);
//前置++
++d1;
//后置++
d1++;
虽然后置++多了一个参数
但是不需要我们显示传参
编译器会自动帮助我们传参!
3.4 运算符重载再理解
先看以下日期类代码:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
现在定义类对象进行操作:
Date d1(2023,7,31);
Date d2(2023,7,30);
if(d1==d2)
{
cout<<"d1和d2相同";
}
Date d3;
d3 = d1;
上面的代码中调用了两个运算符重载函数
operator=
和operator==
实际上调用 == 时是这样的:
调用 = 时是这样的:
4. 总结以及拓展
写运算符重载函数时,尽量使用引用传参
const Date& d
若传值传参,会调用拷贝构造,效率很低
有几个操作符不能被运算符重载:
.*
::
sizeof
?:
.
C++中拷贝构造和赋值运算符容易混淆
Date d1(2023,7,31);
Date d2 = d1;
Date d3;
d3 = d1;
d2=d1是调用了拷贝构造函数
d3=d1是调用了赋值运算符重载
关于更多拷贝构造和赋值的关系
可以参考这篇文章:
拷贝构造和赋值运算符重载