为什么需要赋值运算符
拷贝构造函数只能在初始化进行状态的赋予,如果需要在初始化后再进行状态整体的赋予需要用到复制构造运算符(也叫做复制构造函数)
什么是赋值运算符
赋值运算符本质上是一个函数,函数名为=
。它发生的时期是在除初始化之外的状态赋予。基本格式是
class A
{
A & operator=(const A &);
}
A A::operator=(const A &)
{
return *this;
}
返回值为左侧运算对象的引用。(标准库通常要求定义赋值运算符)
定义
- 类T的复制赋值运算符是名为
operator=
的非模板非静态成员函数,它接收恰好一个 T、T&、const T&、volatile T& 或 const volatile T& 类型的形参。 - 对于可复制赋值 (CopyAssignable) 类型,它必须有公开的复制赋值运算符
语法
类名 & 类名 :: operator= ( 类名 ) | (1) |
---|---|
类名 & 类名 :: operator= ( const 类名 & ) | (2) |
类名 & 类名 :: operator= ( const 类名 & ) = default; | (3) |
类名 & 类名 :: operator= ( const 类名 & ) = delete; | (4) |
- 当可以使用复制并交换手法时,复制赋值运算符的典型声明。
- 当不能使用复制并交换手法时(不可交换类型或有性能退化),复制赋值运算符的典型声明。
- 强制编译器生成复制赋值运算符。
- 避免隐式复制赋值。
凡在凡在为重载决议所选择时,复制赋值运算符得到调用,例如对象出现在赋值表达式左侧时。
隐式声明的复制赋值运算符
即使我们没有显式的重载赋值运算符,编译器也会以默认地方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,这和默认拷贝构造函数的功能类似。
对于简单的类,默认的赋值运算符一般就够用了,我们也没有必要再显式地重载它。但是当类持有其它资源时,例如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认的赋值运算符就不能处理了,我们必须显式地重载它,这样才能将原有对象的所有数据都赋值给新对象。
#include <iostream>
#include <cstdlib>
using namespace std;
//变长数组类
class Array{
public:
Array(int len);
Array(const Array &arr); //拷贝构造函数
~Array();
public:
int operator[](int i) const { return m_p[i]; } //获取元素(读取)
int &operator[](int i){ return m_p[i]; } //获取元素(写入)
Array & operator=(const Array &arr); //重载赋值运算符
int length() const { return m_len; }
private:
int m_len;
int *m_p;
};
Array::Array(int len): m_len(len){
m_p = (int*)calloc( len, sizeof(int) );
}
Array::Array(const Array &arr){ //拷贝构造函数
this->m_len = arr.m_len;
this->m_p = (int*)calloc( this->m_len, sizeof(int) );
memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}
Array::~Array(){ free(m_p); }
Array &Array::operator=(const Array &arr){ //重载赋值运算符
if( this != &arr){ //判断是否是给自己赋值
this->m_len = arr.m_len;
free(this->m_p); //释放原来的内存
this->m_p = (int*)calloc( this->m_len, sizeof(int) );
memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}
return *this;
}
//打印数组元素
void printArray(const Array &arr){
int len = arr.length();
for(int i=0; i<len; i++){
if(i == len-1){
cout<<arr[i]<<endl;
}else{
cout<<arr[i]<<", ";
}
}
}
int main(){
Array arr1(10);
for(int i=0; i<10; i++){
arr1[i] = i;
}
printArray(arr1);
Array arr2(5);
for(int i=0; i<5; i++){
arr2[i] = i;
}
printArray(arr2);
arr2 = arr1; //调用operator=()
printArray(arr2);
arr2[3] = 234; //修改arr1的数据不会影响arr2
arr2[7] = 920;
printArray(arr1);
return 0;
}
运行结果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 3, 4
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
将 arr1 赋值给 arr2 后,修改 arr2 的数据不会影响 arr1。如果把 operator=() 注释掉,那么运行结果将变为:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 3, 4
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 234, 4, 5, 6, 920, 8, 9
去掉operator=()后,由于 m_p 指向的堆内存会被 free() 两次,所以还会导致内存错误。
下面我们就来分析一下重载过的赋值运算符。
- operator=() 的返回值类型为Array &,这样不但能够避免在返回数据时调用拷贝构造函数,还能够达到连续赋值的目的。下面的语句就是连续赋值:
arr4 = arr3 = arr2 = arr1;
- if( this != &arr)语句的作用是「判断是否是给同一个对象赋值」:如果是,那就什么也不做;如果不是,那就将原有对象的所有成员变量一一赋值给新对象,并为新对象重新分配内存。下面的语句就是给同一个对象赋值:
arr1 = arr1;
arr2 = arr2;
-
return *this表示返回当前对象(新对象)。
-
operator=() 的形参类型为const Array &,这样不但能够避免在传参时调用拷贝构造函数,还能够同时接收 const 类型和非 const 类型的实参
-
赋值运算符重载函数除了能有对象引用这样的参数之外,也能有其它参数。但是其它参数必须给出默认值,例如:
Array & operator=(const Array &arr, int a = 100);
如何使用赋值运算符?
就和正常的赋值一样使用就行。
注意
- 等号=不一定是赋值运算符,发生在初始化时可能是拷贝构造函数或移动构造函数。