本節書摘來自異步社群出版社《c++面向對象高效程式設計(第2版)》一書中的第3章,第3.4節,作者: 【美】kayshav dattatri,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
c++面向對象高效程式設計(第2版)
現在,讓我們分析main程式中的語句:
<code>a = b ; // 将一個棧指派給另一個棧</code>
在該語句中,我們将對象b指派給對象a,使用指派操作符完成指派操作。如果a和b都是簡單的整數,無論a中的值是什麼,編譯器都會用b中的值将其擦寫(overwrite)。就是這麼簡單。但是在該例中,a和b都是我們建立的對象,這意味着由我們負責指派操作。我們知道tintstack類對象之間如何進行指派,并且能夠實作指派操作符。對于任何指派操作符,都應注意以下幾點:
(1)確定對象沒有自我指派(如a = a)。
(2)複用被指派對象中的資源或銷毀它。
(3)從源對象中将待複制内容複制到目的對象。
(4)最後,傳回對目的對象的引用。
指派操作符已在類中聲明。tintstack類的指派操作符簽名如下:
tintstack& tintstack::operator=(const tintstack& source)
{
// 檢查自我複制(即a = a)。
// source是被指派的對象,&source給出對象source的位址,
// ‘this’是被指派對象的位址。
if (this == &source) {
cout << “warning: assignment to self.n”;
return *this;
}
/*
如果源對象棧中的元素數目小于或等于目的對象的大小,則沒有任何問題。
我們要做的就是複制棧中相應的元素和_count變量。
但是,如果源對象棧中元素的數目大于目的對象的大小,則必須先删除目的對象_sp中記憶體,再為其配置設定新的記憶體(與源對象棧中元素的數目相等),然後複制所有元素。
*/
if (source._count > this->_size) {
// 源對象中元素的數目大于目的對象的大小,
// 删除_sp中的記憶體并重新配置設定記憶體。
// 見下文解釋
delete [] _sp;
this->_size = source._size;
_sp = new int [ this->_size ];
// 無論是小于等于還是大于的情況,都會用到以下代碼周遊棧,
// 并複制元素
for (int i = 0; i < source._count; i++)
this->_sp[i] = source._sp[i]; // 複制元素
this->_count = source._count;
return *this; // 見下文解釋
}<code>`</code>
分析:指派操作由程式員顯式完成,系統(編譯器)絕不會直接調用它。指派是在兩個現有對象之間執行的操作。如下語句:
`
a = b;`
當a和b都為對象時,上句可解析為:
operator=(b);`
換言之,左側(left hand side,縮寫為lhs)的對象調用成員函數operator=(),右側(right hand side,縮寫為rhs)操作數作為operator=()的參數。指派操作右側的對象(該例中為b)不能被修改,該操作的副作用(side effect)是:傳回對左側對象(該例中為a)的引用。因為不能修改右側的對象(我們隻能讀取右側對象并寫入左側對象),是以該對象應作為const實參傳遞給operator=()函數(如②所示)。然而,如果使用沒有const參數的指派操作符(如①所示),就可以被修改右側的對象。我們并不推薦使用①這樣的指派操作符,因為類的使用者并不希望在指派操作中改變右側的對象。由于a和b都是真正的現有對象,是以,将b指派給a時,a中的值将被b中的值擦寫(overwrite)。
記住:
如果我們在類中未提供指派操作符,編譯器會為類生成一個預設指派操作符(編譯器絕不會自動調用它,但确實會生成一個)。但是,這樣的預設指派操作符可能無法滿足我們的要求,下一章将對此作詳細介紹。
在任何指派操作中,我們要做的就是将資料從源對象指派到目的對象。一般而言,這很容易,但在某些情況下會有困難。在tintstack的示例中,我們需要從源棧指派到目的棧。如果源棧中的元素數目少于或等于目的棧中的可用空間,指派操作便非常簡單,隻需複制相應的棧元素。但是,如果源棧中的元素數目多于目的棧所能持有的數目會怎樣?我們不允許這樣的操作發生,在列印出錯誤消息後将從指派操作傳回。不過,這樣限制太大。或者,我們可以改變目的棧的大小,使其能容納所有元素。如果我們接受後者的方案,就需要在目的棧上為源棧的元素配置設定與源棧大小相等的新記憶體。這樣做之前,我們還要删除目的棧中_sp所指向的現有記憶體(無法擴充這個記憶體),這就是上面的指派操作符中調用delete所完成的任務。一旦完成記憶體配置設定,我們就隻需要複制相應的元素進棧即可(見圖3-3和圖3-4)。
(摘自前面的main程式)
class tstring {
public:
tstring (const char* sp) {
_data = new char[strlen(sp) + 1];
strcpy(_data, sp);
}
tstring & operator=(const tstring & assign);
private:
char* _data; // 字元串指針
};
tstring& tstring::operator=(const tstring& assign)
// 錯誤代碼,未檢查自我指派
delete [] this->_data; // ①
data = new char[strlen(assign.data) + 1]; // ②
strcpy(this->_data, assign._data);
return *this;
}
tstring x1(“text string1”);
x1 = x1;<code>`</code>
以上<code>operator=</code>的實作并不安全,其中的①,删除的是x1._data。接着在②中,我們又試圖使用assign._data。因為左側的對象(*this)與右側的對象(assign)是同一個對象,即assign._data與x1._data相同。是以,我們搬起石頭砸自己的腳。當執行②時,該程式可能崩潰,或者在别的地方出現問題。是以,在指派操作的實作中,必須進行自我指派檢查。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLlF2M2YWN0IGMyYWOjdTMiJDMxETY3MWYwQjNzIDOlhDZ3Y2MycDMw8CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
圖3-4
将b指派給a非常簡單,之前的示例中已介紹過指派如何工作。我們希望将指派(b指派給a)的結果再指派給c。為使其正常工作,第1個指派操作應傳回a的值(或對a的引用)。否則,從a到c的第2次指派就會出問題。通常,在c和c++中,我們隻知道使用指派操作的結果,卻并不了解内部的實作。例如,正是因為指派操作的副作用,才使得以下代碼可以正常工作:
void tintstack::push(int what)
if (_count < _size) { // 如有更多空間儲存元素
_sp[_count] = what; // 儲存元素
_count++; // 遞增count
}
else {
cout << “stack is full. cannot push value” << what << endl;
int tintstack::pop()
if (_count <= 0) { // 棧為空
cout << “stack is emptyn”;
exit (1); // 如果失敗如何報錯?可在此處抛出異常。
_count--;
return _sp[_count];
unsigned tintstack::howmany() const
return _count;
1此處用到操作符重載和操作符結合律,将在第8章中介紹。在本章讨論中,隻需記住指派從右向左結合。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。