目录
1.赋值运算符重载函数是将一个已经存在的对象拷贝赋值给另一个已经存在的对象,而拷贝构造函数是一个已经纯在的对象拷贝给另一个刚要创建初始化的对象
2.赋值运算符重载函数要有返回值(上面写的是为了先简单理解),有返回值目的是为了支持连续赋值
4.拷贝构造函数
4.1概念
在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2特性
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
下面解释一下为什么参数要是引用类型
C++类和对象中篇2
// 视频中的代码,可以自己下去试试
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 1;
};
void fun(Date d)
{
d.Print();
}
int main()
{
Date d1(2024, 4, 24);
fun(d1);
return 0;
}
视频中可以看出调用fun(Date d)这个函数时,是先调用Date(Date& d)拷贝构造函数,为什么呢?
调用函数之前是先传参,对于内置类型(int、double...)是直接拷贝赋值给形参,对于自定义类型(类、结构体...)就要调用拷贝构造进行赋值给形参,传参完成后才会进入到函数内部去。
在调用函数是fun(d1)时,因为要先传参并且d1为自定义类型,所以会去调用Date(Date& d),那这里是谁调用呢?是fun(Date d)函数中形参d进行调用的,相当于Date d(d1),把d1中的值拷贝给形参d,打印出来的值与d1相同。
如果拷贝构造函数的参数不写成引用,逻辑上就会引发无限递归
里面参数写成引用就是别名,就不会调用拷贝构造函数,就不会造成无穷递归。
通常我们写函数里面形参是自定义类型基本上用的是引用,这样就不会调用拷贝构造函数,节省时间,提高程序效率。
还有一点忘提了,就是参数加个const更好
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
/*Date(Date& d)
{
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2024;
int _month = 1;
int _day = 1;
};
int main()
{
Date d1(2024, 4, 25);
Date d2 = d1;
d2.Print();
return 0;
}
我们把显示定义的拷贝构造函数注释掉
运行结果说明拷贝成功了,编译器生成默认的拷贝构造函数完成了拷贝。
接下来我们看一下Stack类,类里我们没写拷贝构造
// 简写
typedef int DataType;
class Stack
{
public:
// 构造函数
Stack(int capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (_array == NULL)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void push(DataType x)
{
_array[_size++] = x;
}
// 析构函数
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s1;
s1.push(1);
s1.push(2);
s1.push(3);
Stack s2 = s1;
return 0;
}
为什么会这样呢?我们调试看看
我们看到的是拷贝成功了,不过s1._array 等于 s2._array; 它们两个指针指向的是同一块空间,默认拷贝构造函数按照值拷贝,将s1中的内容原封不动的拷贝到s2中,当程序退出时,s1和s2都要销毁,s2先销毁,s2调用析构函数,将_array指向的空间释放了,当s1销毁时,会将_array指向的空间再释放一次,于是一块空间多次释放,就造成了程序崩溃。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
5.赋值运算符重载
5.1运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其 返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号(+、-、=、==、...)。
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
.* :: sizeof ?: . 注意这5个运算符不能重载
操作符分为双操作符(+、-、=、...要有两个操作数进行运算)和单操作符(++、--、...单个操作数进行运算)
运算符重载函数的参数个数==操作数个数(+就要有两个参数,++一个参数就行)
上面演示的是在全局写的运算符重载函数,下面我们把它写到类中
5.2赋值运算符重载
拷贝构造和这个简单的赋值里面内容都差不多,那有啥区别呢?这里这个赋值运算符重载函数还未写全,接下来我们一步一步看。
1.赋值运算符重载函数是将一个已经存在的对象拷贝赋值给另一个已经存在的对象,而拷贝构造函数是一个已经纯在的对象拷贝给另一个刚要创建初始化的对象
2.赋值运算符重载函数要有返回值(上面写的是为了先简单理解),有返回值目的是为了支持连续赋值
里面有一个隐藏的参数Date* const this;不能改变this,但能改变this指向的空间里面的值,
当我们写下 d2 = d1,d2的地址就赋值给this,参数d就是对d1的引用(d就表示d1),所以返回*this就是返回d2.
3.为什么要返回引用
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 1)
{
cout << "构造" << endl;
_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)
{
cout << "赋值重载" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
~Date()
{
cout << "析构" << endl;
_year = -1;
_month = -1;
_day = -1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2014, 4, 27);
Date d2 = d1;
Date d3;
d3 = d1;
return 0;
}
这个是上面代码的运行结果,接下来我们把引用去掉
为什么呢?
所以多了一次拷贝构造,多的一个析构就是销毁这个临时对象时调用的;当我们加上引用,就不会产生临时对象进行拷贝构造,返回的是它的别名。
为了防止自己给自己赋值(d1 = d1),所以加上一个判断