BUG现形记(二)——偷工减料的复制构造函数
最后更新于:2022-04-01 14:38:03
【课程支撑】[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)
【摘要】设计数组类,要实现数组类中两个数组相加的运算,程序却陷入死循环。逐层排查,重载的加法正确,重载的赋值运算也看不出问题。跟踪到赋值运算的实现中发现,传递的参数中有异常,终于找出了嫌疑犯——编制的复制构造函数偷工减料。
【阅读提示】现在打开你熟悉的c++,跟随作者的的思路,重走发现嫌犯的过程。
题目是建立专门的数组类处理有关数组的操作,要完成支持数组操作的类的设计,增强C++内置数组类型功能。——见:[第14周-任务1-数组类的构造](http://blog.csdn.net/sxhelijian/article/details/7587387)
有同学向我求助,他的程序如下:
~~~
#include <iostream>
using namespace std;
class MyArray
{
private:
int *arr; //用于存放动态分配的数组内存首地址
int size; //数组大小
public:
MyArray(int sz=50);
MyArray(int a[],int sz); //由一个内置类型的数组初始化
MyArray(const MyArray &A); //拷贝构造函数
~MyArray(void); //析构函数,注意释放空间
MyArray&operator =(const MyArray &A); //重载“=”使得数组对象可以整体赋值
int& operator[](int i); //重载[],使得Array对象也可以如C++普通数组一样,用a[i]形式取出值【选做】
bool operator == (MyArray& A); //重载==,使得Array对象能整体判断两个数组是否相等(size相等且对应元素相等)
MyArray operator + (MyArray& A); //重载+,使两个Array对象可以整体相加(前提大小相等)【选做】
friend ostream& operator << (ostream& out,MyArray& A); //重载<<,输出数组
int GetSize(void)const; //取数组大小;
void Resize(int sz); //修改数组的大小,如果sz大于数组的原大小,增加的元素初始为;sz小于数组的原大小,舍弃后面的元素【选做】
};
MyArray::MyArray(int sz)
{
size = sz;
arr = new int[size];
for( int i = 0; i < size; i++ )
{
*(arr + i) = 0;
}
}
MyArray::MyArray(int a[],int sz) //由一个内置类型的数组初始化
{
size = sz;
arr = new int[size];
for(int i = 0; i < size; i++)
{
*(arr + i) = *(a + i);
}
}
MyArray::MyArray(const MyArray &A) //拷贝构造函数
{
arr = new int[A.size];
for(int i = 0; i < A.size; i++)
{
*(arr + i) = *(A.arr + i);
}
}
MyArray::~MyArray(void) //析构函数,注意释放空间
{
delete[]arr;
}
MyArray& MyArray::operator =(const MyArray &A) //重载“=”使得数组对象可以整体赋值
{
int n = A.size;
if( size != n )
{
delete[]arr;
arr = new int[n];
size = n;
}
int* destptr=arr;
int* srcptr=A.arr;
while(n--)
{
*destptr=*srcptr;
destptr++;
srcptr++;
}
return *this;
}
int& MyArray::operator[](int i) //重载[],使得Array对象也可以如C++普通数组一样,用a[i]形式取出值【选做】
{
return arr[i];
}
bool MyArray::operator == (MyArray& A) //重载==,使得Array对象能整体判断两个数组是否相等(size相等且对应元素相等)
{
bool m;
m = true;
if( A.size != size )
{
m = false;
}
else
{
for( int i = 0; i < size; i++ )
if( *(A.arr + i) != *(arr + i) )
{
m = false;
break;
}
}
return m;
}
MyArray MyArray::operator + (MyArray& A)
{
int n=A.size; //取A数组的大小
if (size!=n) //大小不一致不能相加
{
cout<<"not same size for add!"<<endl;
exit(1);
}
MyArray a(n); //指定size的数组
for (int i = 0; i < size; i++)
{
a[i]=arr[i]+A[i];
}
return a;//返回当前对象的引用
}
ostream& operator << (ostream& out,MyArray& A) //重载<<,输出数组
{
for( int i = 0; i < A.size; i++)
{
out << A[i] << " ";
}
out << endl;
return out;
}
int MyArray::GetSize(void)const //取数组大小;
{
return size;
}
void MyArray::Resize(int sz) //修改数组的大小,如果sz大于数组的原大小,增加的元素初始为;sz小于数组的原大小,舍弃后面的元素【选做】
{
int *m = new int(sz);
for( int i = 0; i < sz; i++)
{
*m = 0;
}
int n = ( sz <= size )?sz:size;
for(int j = 0; j < n; j++)
{
*(m + j) = *(arr + j);
}
delete[]arr;
arr = m;
}
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
int b[10]={4,5,6,7,8,9,10,11,12,13};
MyArray arr1(a,10);
MyArray arr2(b,10);
MyArray arr3(10);
cout<<arr3;
arr3 = arr1 +arr2;
cout<<arr3;
// arr3.Resize(20);
// cout<<arr3;
// arr3.Resize(5);
// cout<<arr3;
system("pause");
return 0;
}
~~~
运行程序,结果如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_5756400be11e0.JPG)
光标在第二行一闪一闪,就是不见下文。
此症状一般是陷入了死循环。经阅读程序,已经输出的多个0是160行cout<<arr3;的结果。162行同样的语句应该不会出现问题。焦点锁定在第161行:arr3=arr1+arr2。
arr3=arr1+arr2涉及到两个运算符的重载:+ 和 =。
认真地读一下这两个重载函数,比较明显的是第115行的a[i]=arr[i]+A[i];有些别扭。a和A是MyArray类的对象,而arr是当前对象的一个成员(我想起一条胳膊和一个人要加起来,哪能这样!)修改为a.arr[i]=arr[i]+A.arr[i];(相当于a.arr[i]=this->arr[i]+A.arr[i];这就是胳膊和胳膊加了)。这样,对加法结果正确有了把握。
再次运行,问题依旧。也看不出明显的线索。请来法宝,祭起查找Bug的照妖镜——调试工具来帮忙。
在161行上设置断点运行程序,用F11逐语句执行,进入MyArray::operator +() 函数,即对加法的重载直至结束,未见异常。作为返回值的临时对象a,其中的结果正确。a 的两个属性 size 及 arr 和 arr 为起始地址指向的值,也恰好是该求得值的结果。可以在117行也设置一个断点,观察直至此时 相加结果 a 的值。下面是执行到此处时,从局部变量窗口看到的结果(如果要看a.arr[1]及之后的值,可以用监视窗口):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_57564047ef381.JPG)
单步跟踪到MyArray::operator =()函数,即对赋值的重载。很意外地,函数进入到了第59行的 if 语句中。目前程序的运行,所用到的数组,其 size 均为10,应该这个 if 分支是执行不到的。而此时,局部变量的值却让人大吃一惊:MyArray::operator =()中形式参数 A 的 size 成员的值是一个负数!见下图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-07_575640481af11.JPG)
此时该整理一下其中存在的问题了:
(1)问题出在执行arr3=arr1+arr2;上;
(2)计算arr1+arr2,写成函数调用形式是arr1.operator+(arr2),函数调用时,返回的结果是正确的;
(3)接着计算赋值,MyArray::operator =(const MyArray &A) 的形式参数 A 对象,将获得 arr1+arr2 的结果,即实参是 arr1+arr2 的结果,更直观地,执行的是 arr3.operator=(arr1+arr2); ,计算结果本来是正确的,但实参传值给形参后,对象的 size 成员出错了。
在实参给形参传值时,采用的是复制的办法,对类(对象)而言,需要有一个复制构造函数支撑(这个学过)。复制构造函数可以是默认的。但是,如果类中有指针类型的成员时,必须自定义复制构造函数,从而能够处理指针所指向空间的分配和回收问题。
所以,嫌疑犯基本锁定:MyArray类的复制构造函数(也称拷贝构造函数)。看41行开始的拷贝构造函数MyArray::MyArray(const MyArray &A) 的定义,为this->arr成员分配了空间,并将形参对象中指向的数据一一进行复制,惟独没有做的,是为 this->size 赋值!我们需要在这个函数中,增加一行代码:size = A.size;。
至此,案情大白于天下,错误得以纠正,程序得以正确运行。
相关博文:[寻找Bug记实:一个多重继承程序的查错](http://blog.csdn.net/sxhelijian/article/details/7551594)
课程支撑:[我的 C++程序设计课程教学材料](http://blog.csdn.net/sxhelijian/article/details/7056008)