记录一下类运算符重载等相关练习时遇到的细节和坑。
在用类定义一种新数据类型的时候是没有与之相匹配的“+”,“<<”等运算符的,如果要使用的话就要自己对其进行重载,编写相匹配的运算符函数。
如果类内成员开辟了堆内存一定要先写深拷贝函数,并且重载“=”类赋值运算符。
这里以一个数组类为例:
class c_array
{
private:
int arr_size;
int* arr;
};
c_array类的成员分别是arr_size以及arr,分别为数组的元素个数以及数组的存放地址,此时先定义了构造函数,根据输入的维数arr_size在堆上的定义一个数组,由于开辟了堆内存所以定义了一个深拷贝函数。
在定义完各种构造函数和析构函数后,此时进行运算符的重载,如果想实现两个数组相加等于对应元素相加,与数相加等于每个元素等加上这个数,自增同理,相当于自身每个元素+1:
c_array a1;
c_array a2(a1);
c_array b = a1 + a2 + 10000;
++b;
需要定义重载的加号运算符,分别为两个c_array类相加的加法以及c_array与常数相加的加法:
c_array c_array::operator+(c_array& a)
{
//可以用全局函数重载类运算符(此时不能用this指针,需要两个类参数传入)
c_array temp(arr_size);
for (int i = 0; i < arr_size; i++)
{
temp.arr[i] = this->arr[i] + a.arr[i];
}
return temp;//加法不修改输入值,所以返回值而非引用
}
c_array c_array::operator+(const int& a)
{
c_array temp(arr_size);
for (int i = 0; i < arr_size; i++)
{
temp.arr[i] = this->arr[i] + a;
}
return temp;
}
此时用成员函数来重载两个加法运算符,如果使用全局函数重载,则需要多传入一个c_array类参数并且不能使用this指针。由于加法是值运算,不修改参与加法的变量的原始数据,所以只能使用值传递而非地址传递,使用临时变量记录运算值并返回。
此时利用重载的加法实现c_array类的++自增运算符:
c_array& c_array::operator++()//前置++
{
*this = *this + 1;
return *this;
}
c_array c_array::operator++(int)//后置++,占位符区分
{
c_array temp = *this;
*this = *this + 1;
return temp;//修改*this,返回记录的temp的值
}
分别是前置自增和后置自增运算符,后置递增通过参数中的int占位符区分。
我们知道前置递增是先自增再进行外部运算,后置递增是先做外部运算再进行自增,为了实现这一点,前置递增返回自身的引用(直接返回修改后的原始数据*this),后置递增返回临时变量(原始数据*this也进行了修改,但是此时先返回修改之前记录的temp)。
但是在测试时发现此时的自增运算出现bug,在调试过程中自增后的值为乱码,且主程序在运行到最后调用析构函数释放堆内存时崩溃。
根据bug的表现首先怀疑*this = *this + 1;这一行代码,可能是出现了this指针被加法中临时变量地址赋值,由于临时变量在函数运行完就会被释放,所以this指向的地址为空,因此出现了乱码,并且整个程序运行完释放堆空间时试图再次析构野指针this的空间,产生了越界行为致使程序崩溃。
虽然这种情况和bug表现能对上,但是显然代码逻辑并不符合以上想法,自增函数中并没有对this指针进行修改,加法中的临时变量也传递的是temp的值而非地址。为了找到问题所在我打印了加法中的临时变量temp和自增中的*this的地址,结果this和第一个&temp相同,这说明temp确实直接变成了*this,临时变量temp被释放后this变成了野指针。
此时加法temp出现了两次,两次分别为主函数中
c_array b = a1 + a2 + 10000;
++b;
这两行代码产生的,而++中的this与第一个temp也就是第一行对c_array b进行赋值时+10000产生的temp地址一致。
联想到程序崩溃的原因,问题的关键在于赋值运算符“=”没有重载,编译器提供的默认赋值运算是将成员的值直接赋值给对象成员,也就是浅拷贝,导致c_array b在定义时其中的*arr成员被直接赋被浅拷贝为了a1+a2+10000中的第二个+号中temp变量的地址。
要解决这个问题必须要手动重载一个深拷贝的类赋值运算:
c_array& c_array::operator=(const c_array& a)
{
//默认的 类赋值运算符 为浅拷贝实现,如果开辟了堆区内存需要手动重载 深拷贝类赋值,不然会重复析构
if (this->arr != NULL)
{
delete[] this->arr;
this->arr = NULL;
}//先把本体清空,再赋新值
this->arr_size = a.arr_size;
this->arr = new int[a.arr_size];
for (int i = 0; i < a.arr_size; i++)
{
this->arr[i] = a.arr[i];
}
return *this;
}
此时程序可以按照理想情况运行,不会再产生野指针。