最近在學習運算符重載和構造析構函數的時候,在重載+的時候,等號左邊的值經常出現一個問題,經過一天多的研究,終於將這個問題解決了,現在將這個問題的解決方法記錄下來,供大家互相學習。初次發文,不足之處還請多多包含!
問題:重載+之後,等號左邊的值出現錯誤。
解決方案:重載=。以下是一些詳細的說明。
/*--------------------------------------------
寫一個字符串類CString
1.重載+,實現“123”+“456”(=“123456”);
2.重載+=,實現“123”+=“456”(=“123456”);
3.重載=;
4.重寫拷貝構造函數;
--------------------------------------------*/
#include <string.h>
#include <stdio.h>
class CString
{
char *m_str;
int m_Length;
public:
/*函數作用:無參構造函數*/
CString();
/*函數作用:用字符指針進行有參構造
參數 ch:一個指向字符串的指針*/
CString(const char * ch);
/*函數作用:拷貝構造
參數 str:一個CString類型的對象,以此對象爲模板,創建一個一模一樣的對象*/
CString(const CString& str);
/*函數作用:通過重載運算符+,實現將兩個字符串相加的操作
參數 str:相加的第二個CString對象
返回值:兩個對象相加後的結果*/
CString operator+(const CString& str) const;
/*函數作用:通過重載運算符+=,實現將一個字符串添加到另一個字符串末尾的操作
參數 str:被添加到當前字符串(調用此函數的那個字符串)後面的CString對象*/
void operator+=(const CString& str);
/*函數作用:通過重載運算符=,實現賦值操作
參數 str:源對象*/
void CString::operator=(const CString & str);
/*函數作用:返回對象中的字符串
返回值:指向對象中字符串的指針*/
char *GetStr();
/*函數作用:析構函數 清空保存對象的內存空間,並釋放*/
~CString();
};
int main()
{
CString str1("123");
CString str2("456");
CString str3;
CString str4(str1 + str2); (1)
CString str5 = str1 + str2; (2)
str3 = str1 + str2; (3)
printf("%s+%s=%s\n", str1.GetStr(), str2.GetStr(), str4.GetStr());
printf("%s+%s=%s\n", str1.GetStr(), str2.GetStr(), str3.GetStr());
printf("str1=%s,str2=%s,", str1.GetStr(), str2.GetStr());
str1 += str2;
printf("str1+=str2,str1=%s\n", str1.GetStr());
return 0;
}
CString::CString()
{
}
CString::CString(const char * ch)
{
m_Length = strlen(ch);
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, ch);
}
CString::CString(const CString & str)
{
//*this = str;(這個行語句調用了重載的運算符=,可以替代下面的三行語句。這行語句和下面的三行語句二選一)
m_Length = str.m_Length;
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, str.m_str);
}
CString CString::operator+(const CString & str) const
{
CString str_new;
str_new.m_Length = m_Length + str.m_Length;
str_new.m_str = new char[str_new.m_Length + 1];
strcpy_s(str_new.m_str, m_Length + 1 , m_str);
strcat_s(str_new.m_str, str_new.m_Length + 1, str.m_str); //這裏需要注意的是第二個參數爲目標字符串緩衝區的大小
return str_new; //return是先調用拷貝構造函數創建一個跟str_new對象一模一樣的臨時對象Temp,並且返回,然後調用析構函數,將對象str_new析構掉
}
void CString::operator+=(const CString & str)
{
char *ch = new char[m_Length + 1];
strcpy_s(ch, m_Length + 1, m_str);
int length = strlen(str.m_str);
m_Length += length;
delete m_str;
m_str = NULL;
m_str = new char[m_Length + 1];
strcpy_s(m_str, strlen(ch) + 1, ch);
strcat_s(m_str, m_Length + 1 , str.m_str);
delete[] ch;
ch = NULL;
}
void CString::operator=(const CString & str)
{
m_Length = strlen(str.m_str);
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, str.m_str);
}
char * CString::GetStr()
{
return m_str;
}
CString::~CString()
{
delete[] m_str;
m_str = NULL;
}
寫一個字符串類CString
1.重載+,實現“123”+“456”(=“123456”);
2.重載+=,實現“123”+=“456”(=“123456”);
3.重載=;
4.重寫拷貝構造函數;
--------------------------------------------*/
#include <string.h>
#include <stdio.h>
class CString
{
char *m_str;
int m_Length;
public:
/*函數作用:無參構造函數*/
CString();
/*函數作用:用字符指針進行有參構造
參數 ch:一個指向字符串的指針*/
CString(const char * ch);
/*函數作用:拷貝構造
參數 str:一個CString類型的對象,以此對象爲模板,創建一個一模一樣的對象*/
CString(const CString& str);
/*函數作用:通過重載運算符+,實現將兩個字符串相加的操作
參數 str:相加的第二個CString對象
返回值:兩個對象相加後的結果*/
CString operator+(const CString& str) const;
/*函數作用:通過重載運算符+=,實現將一個字符串添加到另一個字符串末尾的操作
參數 str:被添加到當前字符串(調用此函數的那個字符串)後面的CString對象*/
void operator+=(const CString& str);
/*函數作用:通過重載運算符=,實現賦值操作
參數 str:源對象*/
void CString::operator=(const CString & str);
/*函數作用:返回對象中的字符串
返回值:指向對象中字符串的指針*/
char *GetStr();
/*函數作用:析構函數 清空保存對象的內存空間,並釋放*/
~CString();
};
int main()
{
CString str1("123");
CString str2("456");
CString str3;
CString str4(str1 + str2); (1)
CString str5 = str1 + str2; (2)
str3 = str1 + str2; (3)
printf("%s+%s=%s\n", str1.GetStr(), str2.GetStr(), str4.GetStr());
printf("%s+%s=%s\n", str1.GetStr(), str2.GetStr(), str3.GetStr());
printf("str1=%s,str2=%s,", str1.GetStr(), str2.GetStr());
str1 += str2;
printf("str1+=str2,str1=%s\n", str1.GetStr());
return 0;
}
CString::CString()
{
}
CString::CString(const char * ch)
{
m_Length = strlen(ch);
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, ch);
}
CString::CString(const CString & str)
{
//*this = str;(這個行語句調用了重載的運算符=,可以替代下面的三行語句。這行語句和下面的三行語句二選一)
m_Length = str.m_Length;
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, str.m_str);
}
CString CString::operator+(const CString & str) const
{
CString str_new;
str_new.m_Length = m_Length + str.m_Length;
str_new.m_str = new char[str_new.m_Length + 1];
strcpy_s(str_new.m_str, m_Length + 1 , m_str);
strcat_s(str_new.m_str, str_new.m_Length + 1, str.m_str); //這裏需要注意的是第二個參數爲目標字符串緩衝區的大小
return str_new; //return是先調用拷貝構造函數創建一個跟str_new對象一模一樣的臨時對象Temp,並且返回,然後調用析構函數,將對象str_new析構掉
}
void CString::operator+=(const CString & str)
{
char *ch = new char[m_Length + 1];
strcpy_s(ch, m_Length + 1, m_str);
int length = strlen(str.m_str);
m_Length += length;
delete m_str;
m_str = NULL;
m_str = new char[m_Length + 1];
strcpy_s(m_str, strlen(ch) + 1, ch);
strcat_s(m_str, m_Length + 1 , str.m_str);
delete[] ch;
ch = NULL;
}
void CString::operator=(const CString & str)
{
m_Length = strlen(str.m_str);
m_str = new char[m_Length + 1];
strcpy_s(m_str, m_Length + 1, str.m_str);
}
char * CString::GetStr()
{
return m_str;
}
CString::~CString()
{
delete[] m_str;
m_str = NULL;
}
語句(1)和語句(2)的執行過程是一樣的。以語句(1)爲例說明其執行順序。
CString str4(str1 + str2); (1)
(1)
第一步:調用運算符+的重載函數,進行計算。計算得到一個局部對象str_new。
第二步:運算符+重載函數返回。在重載函數返回的時候,先調用拷貝構造函數創建一個跟對象str_new一模一樣的臨時對象Temp,並且將臨時對象Temp返回,將臨時對象Temp的名字改成str4。(在這裏對象str4還沒有聲明,索性將返回的對象直接改名字(就是這麼任性!!!),免去了一次定義對象和一次釋放對象的麻煩。爲了方便可以這麼理解,具體操作過程請研究彙編)
第三步:調用析構函數。調用析構函數,將局部對象str_new析構。(第二步和第三步是return語句乾的活)
執行完第三步之後,語句(1)已經執行結束了。
下面說明,語句(3)執行順序。
CString str3;
CString str3;
str3 = str1 + str2; (3)
(3)
第一步:調用運算符+的重載函數,進行計算。計算得到一個局部對象str_new。
第二步:運算符+重載函數返回。在重載函數返回的時候,先調用拷貝構造函數創建一個跟對象str_new一模一樣的臨時對象Temp,並且將對象Temp返回。
第三步:調用析構函數。調用析構函數,將局部對象str_new析構。(第二步和第三步是return語句乾的活)
第四步:調用運算符=重載函數。調用運算符=重載函數,將返回的臨時對象Temp的值進行深拷貝,拷貝給已聲明的對象str3。
第五步:調用析構函數。在完成臨時對象Temp的深拷貝之後,調用析構函數,將臨時對象Temp析構掉。
說明:因爲在此之前,已經有了對對象str3的聲明,因此在執行語句(3)的時候,會進行深拷貝,將臨時對象拷貝給已有對象str3。然後將臨時對象Temp調用析構函數析構掉。
總結
在調用完運算符+重載函數之後,如果=左邊的對象沒有聲明,就直接將運算符+的重載函數返回的臨時變量的名字改爲=左邊的對象的名字。如果=左邊的對象,已經被聲明,以運算符+重載函數返回的臨時變量爲參數,=左邊的對象調用運算符=重載函數。等運算符=的函數返回之後,調用析構函數,將臨時對象析構掉。
注意:在這裏必須重載運算符=。如果不重載運算符=,語句(3)的=這裏會進行淺拷貝。而且,由於已經聲明瞭str3,程序會調用析構函數將運算符+重載函數返回的臨時變量析構掉。這樣,會導致以下兩個問題:
第一、由於進行了淺拷貝,對象str3中的指針和臨時對象中的指針指向了同一塊內存空間。在程序調用析構函數將臨時變量析構之後,對象str3中的指針爲野指針,對象str3不完整了。
第二、由於進行了淺拷貝,在程序結束前,調用析構函數,動態刪除給對象str3的指針分配的內存時會出錯。其實,此塊內存在此之前已經被釋放掉了,重複釋放引發錯誤。