1.拷贝构造函数
我们在创建对象得的时候,可否创造一个与已存在对象一摸一样的对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰)
用在已存在的类类型对象创建新对象时由编译器自动调用
拷贝构造函也是特殊的成员函数,其特征如下:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用其他传值方式编译器直接报错,因为会引发无穷递归!
先来解释一下为啥会无限递归
class Date
{
Date(const Date date)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
private:
int _year;
int _month;
int _day;
};
直接这样写·编译器会报错,为啥?
我这个地方调用Date date 的时候我要干嘛?我要把Date这个类的所有内容都要拷贝过来,
那这个地方我把这个类传过来的同时也把这个函数穿过来了!
那我把这个函数穿过来的时候是不是也把这个函数的Date date传过来了!这样不就又形成了一个新的拷贝构造了吗?
这样一直传一直传,无限递归无法停止
那我们有没有一种好的方法能解决这个问题吗?
答案是当然有啊!
可能在学C语言的时候会想到指针(任何指针都是内置类型)
但是我们现在学了C++所以就用引用(取别名)
我们先来看下面这串代码
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_day = day;
_month = month;
}
public:
Date(const Date& date)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
public :
int _year;
int _month;
int _day;
};
int main(void) {
class Date d1(2005, 4, 14);
Date d2(d1);
return 0;
}
Date d2(d1)和
Date(const Date& date)
{
_year = date._year;
_month = date._month;
_day = date._day;
}
本质上是在干什么?
我们把this指针加上
Date(const Date& date)
{
this->_year = date._year;
this->_month = date._month;
this->_day = date._day;
}
这个地方的d2传给了this,而d1传给了date,所以这个操作的本质上是把d2的值赋给d1!
这个地方虽然成员变量是private
在类里面不受访问限定符的限制
其实你会发现这样编译器也能通过
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_day = day;
_month = month;
}
public :
int _year;
int _month;
int _day;
};
int main(void) {
class Date d1(2005, 4, 14);
Date d2(d1);
return 0;
}
这是因为拷贝构造函数如果没有写显示定义,编译器会生成默认的拷贝构造函数
默认的拷贝构造函数对象按内存储存的字节序完成拷贝
这种拷贝叫做浅拷贝,也称值拷贝!
这个地方编译器对内置对象和自定义对象的处理方式和前面学过的构造函数和析构函数有点区别
1.内置类型成员完成值拷贝/浅拷贝
2.自定义类型成员会调用他的拷贝构造
看起来上面的代码好像默认的拷贝构造函数也够用啊!
但是在有些情况下是不适用的
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
比如说这个栈当作类去写
s1 ,s2两个类
我拷贝构造函数拷贝过去的同时也把a拷贝过去了。这意味我s1,s2两个的a指向的是同一块空间
当s1析构后a指向的空间被释放了,到s2就再析构把这个空间第二次,但是同一块空间不能释放两次,因此就会出错
像上面的就是浅拷贝,只是值的拷贝,因此有些地方要我们自己去实现深拷贝(这个后面会再介绍)
2.运算符重载
我们平时在比较数的大小的时候,很好比,2>1 显而易见的,但是我们有没有思考过,如果是两个自定义类型的呢? 比如我要比较两个日期的大小,编译器无法直接提供比较大小的方法,这个时候就要我们自己写了,而C++给我们提供了一种很好的方式,就是我们自己来写比较的方法
我们自己来写比较两个日期 的方法
但是这个地方明明写的好像没啥问题,但是为啥这个地方编译器会报错啊!
报错说的是传的参数太多,我们漏掉了this指针啊!
所以我们这个地方改一下就可以了!
class Date
{
public:
bool operator>(const Date& x)
{
if (_year == x._year)
{
return true;
}
else if ((_year == x._year) && (_month > x._month))
{
return true;
}
else if ((_year == x._year) && (_month == x._month) && (_day > x._day))
{
return true;
}
return false;
}
Date(int year, int month, int day)
{
_year = year;
_day = day;
_month = month;
}
public:
int _year;
int _month;
int _day;
};
int main(void) {
class Date d1(2005, 4, 14);
class Date d2(2001, 1, 2);
if (d1 > d2)
{
printf("true");
}
else
{
printf("false");
}
return 0;
}
int a = (int)(d1.operator>(d2));
int b = d1 > d2;
int c = operator>(d1, d2);
这个地方
d1>d2本质上是啥
a b c本质上是同样的东西
但是c的这种方式我们不能直接显示写
operator>函数中的实际是这样的
bool operator>(const Date& x)
{
if (this->_year == x._year)
{
return true;
}
else if ((this->_year == x._year) && (this->_month > x._month))
{
return true;
}
else if ((this->_year == x._year) && (this->_month == x._month) && (this->_day > x._day))
{
return true;
}
return false;
}
所以d1>d2
本质上是将d1的地址当作this指针传过去,把d2的别名传过去给了x
操作符重载的要求是至少有一个类成员
不然你重载出来2>3 还是true 就太离谱了!
接下来我们要开始引入,6个默认成员函数之一的赋值重载
3.赋值重载函数
赋值重载函数本质上和上面的操作符重载是差不多的(因为操作符函数重载本质上也是operator函数嘛!)
但是这个地方本质上还是有一定区别的
class Date
{
public:
void operator=(const Date& x)
{
if (_year == x._year)
{
return ;
}
else if ((_year == x._year) && (_month > x._month))
{
return ;
}
else if ((_year == x._year) && (_month == x._month) && (_day > x._day))
{
return ;
}
return ;
}
Date(int year, int month, int day)
{
_year = year;
_day = day;
_month = month;
}
public:
int _year;
int _month;
int _day;
};
int main(void) {
class Date d1(2005, 4, 14);
class Date d2(2001, 1, 2);
class Date d3(2000, 1, 1);
d1 = d2;
d1 = d2 = d3;
return 0;
}
这个地方我们自己写了一个operator赋值函数我们发现d1=d2这个地方没有问题
但是d1=d2=d3这个地方就出问题了,为啥?
首先我们要知道,这条语句的执行顺序是从右到左的,
d2=d3先进行赋值
正常我们普通变量赋值
比如a=b最后得到的结果是b
但是这个地方我们的d1 d2 d3 是自定义类型
它们的赋值是通过赋值函数重载进行的,d2=d3
通过operator这个函数我们知道,结果是void(这个结果的返回值是void)
然后d1=void
这个地方就出错了,我们稍微改一下就可以了
把operator=这个函数的返回值改成Date就可以了!
但是这还不是最优解,最优解就是传别名
传值拷贝返回效率太低了
这样就不是d1=void 而是d1=d2了。
当然了,这个地方我们自己不写,编译器也会自动生成
但是赋值重载函数和一般重载函数的区别在于
赋值运算符只能重载成类的的成员函数,不能重载成全局函数
原因:我们如果在全局写一个赋值重载函数会导致我们在调用的时候到底是调用全局的,还是类的成员函数,当然我们可能说,我们不写类的那个成员函数不就可以了吗?但是复制重载函数是那6个成员函数之一啊!你不写编译器也会自动生成!
那么赋值运算符重载函数和拷贝构造函数有啥区别呢?
赋值运算符重载函数是作用与两个已存在已声明的函数
而拷贝构造函数本质上就带有声明的属性
默认生成赋值重载跟拷贝构造行为一样
1.内置类型成员 值拷贝 浅拷贝
2.自定义类型成员会去调用他的赋值重载
最后一个问题,我们在使用前置++和后置++去进行操作符重载,该怎么区分呢?
一般都是将后置++的参数多一个参数,从而构成函数重载
这个地方的这个参数a没有实际作用,只是为了和前置++做区分而做出的函数重载!因此也可以只写一个int
//前置++
Date& operator ++()
{
*this += 1;
return *this;
}
//后置++
Date& operator ++(int a)
{
Date tmp(*this);
*this += 1;
return tmp;
}