【課程支撐】[我的?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;
}
~~~
運行程序,結果如下:

光標在第二行一閃一閃,就是不見下文。
此癥狀一般是陷入了死循環。經閱讀程序,已經輸出的多個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]及之后的值,可以用監視窗口):

單步跟蹤到MyArray::operator =()函數,即對賦值的重載。很意外地,函數進入到了第59行的 if 語句中。目前程序的運行,所用到的數組,其 size 均為10,應該這個 if 分支是執行不到的。而此時,局部變量的值卻讓人大吃一驚:MyArray::operator =()中形式參數 A ?的 size 成員的值是一個負數!見下圖:

此時該整理一下其中存在的問題了:
(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)