目录
目录:
1:拷贝构造
2:赋值运算符重载
前言:在上一章我们已经学习过了,构造与析构这两个默认成员函数了,接下来让我们一起来学习另外两个重要的默认成员函数。
首先让我们来讲一下默认成员函数这个概念,所谓的默认成员函数,就是只要是类,类成员中就必定会含有这6个默认成员函数,空类也含有这6个默认成员函数。接下来让我们一起来学习拷贝构造与赋值重载吧。
1:拷贝构造
概念:函数名与类名相同,只有一个形参,且该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时编译器自动调用。
拷贝构造的特点:
1:拷贝构造其实是构造函数的一种重载方式。(形参为该类类型对象的引用)
2:拷贝构造只有一个参数,且该参数必须为该类类型对象的引用,如果是该类类型(传值),那么会形成无限递归,编译器在编译的时候会报错。
3: 当我们在一个类中并没有写拷贝构造函数的时候,编译器会自动生成一个默认的拷贝构造函数,这个默认的拷贝构造会按照字节拷贝的方式来进行拷贝,这种方式又被称为值拷贝(浅拷贝)。
4:拷贝构造对于内置类型的成员完成值拷贝,对于自定义类型的成员调用它的拷贝构造,一般自定义类型成员的拷贝构造需要写一个深拷贝构造函数。
注意:当类中没有申请内存资源的时候我们拷贝构造可写可不写,但是当类中申请了内存资源的时候我们就一定要写拷贝构造,不然我们程序会崩溃掉。
我们通过图来解释上面的语法。
那么为啥在传值的时候会报错呢?我们来分析一下
而当我们将拷贝构造的形参设置为,该类类型对象的引用时,引用并不是实参的拷贝,引用是实参的别名,所以不会形成无限的拷贝构造,也不会出错,真正才是拷贝构造规定的语法。
对于语法3
我们已经将我们写的拷贝构造函数给注释掉了,可是为什么我们在用d2拷贝d1的时候,还是完成了拷贝呢?这里是因为我们不写拷贝构造的时候,编译器会自动生成一个拷贝构造来完成值拷贝(浅拷贝)的工作,这也是默认成员函数牛逼的地方。
可是当编译器会自动生成默认的拷贝构造的时候为什么我们还要学习拷贝构造函数的写法呢?
这是因为呀,这个类并没有向内存中申请资源所以可以不用写拷贝构造利用编译器的拷贝构造就能够完成相应的功能,而当我们的类向编译器申请了内存资源呢?那又会发生什么呢?
因为编译器的值拷贝是从内存中按照字节序来进行拷贝的,所以当我们的对象调用析构函数的时候,会对同一块空间完成两次析构,那么就会有非法访问内存的现象,编译器就会运行崩溃。
如下图的描述:
所以我们可以这样认为,我们学习拷贝构造,是为了当我们类对象向内存中申请空间的时候,我们就需要自己来写拷贝构造,这个用来解决申请内存空间的拷贝构造又叫做深拷贝。
比如说下面的一个例子,我们定义一个Stack类
假设我们使用的是它默认的构造函数。
这里的程序就终止了,因为我们出变量的生命周期会自动调用它的析构函数,所以就导致了释放两次同样的内存空间,导致程序崩溃,调用析构函数的顺序与构造变量的顺序相反,在这里应该先调用的是st1的析构,在st调用的时候指针则会访问异常。
所以我们应该自己来写一个构造函数,使得拷贝的时候并不指向同一块空间。
如下代码 这也就可以使我们释放的时候并不是释放同一块空间
当成员变量开辟空间的时候,我们需要自己来写拷贝构造函数,来完成深拷贝。
下面是几个拷贝构造常用的几个场景
1:用一个已知对象去创建另外一个对象
2:函数参数类型为类类型对象
3:函数返回值为类类型对象
到这里我们的拷贝构造就讲解完毕了,然我们进入下一个知识点的讲解。
2:赋值运算符重载函数
在讲赋值运算符重载函数之前,我们得先学一下运算符重载这个知识点。
我们知道对于我们的内置类型(int char double....)变量来说,他们能够使用运算符来直接进行+-*/...等操作,这些变量的操作编译器会自动来识别,而对于自定义类型来说编译器并不能直接翻译,因为自定义类型比较复杂所以编译器并不能够识别,这也就需要程序员自己实现。
运算符重载:用来解决自定义类型对象使用运算符的问题。
函数名为:operator 运算符
其实运算符重载与普通函数的差别,只是运算符重载具有特定的函数名,其他的与普通函数一样。我们用日期类来举例,运算符重载的定义。
这就是一个日期类的运算符重载的定义,与普通函数并没有差别很大。
我们在定义运算符重载的时候需要注意的有以下几点
1:我们并不能用operator运算符来增加新的运算符,必须与我们内置类型相似的。
2:运算符重载的一个参数必须是类类型的,因为运算符重载是用来解决自定义类型的使用运算符的问题。
3:我们不能改变运算符的意思,比如说operator+,我们并不能改变原有+的含义。
4:当运算符重载函数作为类成员函数的时候,函数的形参比操作数永远少1,因为我们会将左操作数当作this指针传进来 。
5: .* :: sizeof ?: .这5个操作数并不能形成运算符重载
有了运算符重载这个知识,我们就可以来学习默认成员函数的赋值运算符重载函数了,
其实赋值运算符重载函数,也就是operator= 这个函数名的实现。
赋值运算符重载函数当我们不写的时候编译器会默认的生成一个值拷贝,赋值运算符对于成员向内存开辟了空间那么我们的赋值运算符就要写深拷贝,与拷贝构造类似需要自己开空间。
当我们使用赋值运算符重载函数的时候必须在类中实现,不能在类外,因为当我们在类外实现的时候,因为编译器会看到我们类中并没有显示的写赋值运算符重载函数,所以就会默认生成,导致会有两个赋值运算符重载函数,会有冲突。
赋值运算符重载的规则
1:返回值为const 类名&
2:参数也是 const 类名& 用引用来提高效率
3:返会*this,*this是左边操作数如 j = i+=5; 这里i+=5返回的是i。
4:检查自己是否给自己赋值(自己给自己赋值不会报错,但是这样没什么意义)
关于前置++与后置++的区分 i++,++i
我们知道++运算符的函数名应该为 operator++,那么我们该如何来区分 前置与后置呢?
在这里我们可能需要利用函数重载的概念来区分,因为他们两的函数名相同,所以我们可以利用参数使得它们区分。
c++规定,在参数中加了如:operator(int)有形参的就是后置++,未有形参的就是前置++