《C++面向对象高效编程(第2版)》——3.4 赋值操作符
本節書摘來自異步社區出版社《C++面向對象高效編程(第2版)》一書中的第3章,第3.4節,作者: 【美】Kayshav Dattatri,更多章節內容可以訪問云棲社區“異步社區”公眾號查看。
3.4 賦值操作符
C++面向對象高效編程(第2版)
現在,讓我們分析main程序中的語句:
a = b ; // 將一個棧賦值給另一個棧
在該語句中,我們將對象b賦值給對象a,使用賦值操作符完成賦值操作。如果a和b都是簡單的整數,無論a中的值是什么,編譯器都會用b中的值將其擦寫(overwrite)。就是這么簡單。但是在該例中,a和b都是我們創建的對象,這意味著由我們負責賦值操作。我們知道TIntStack類對象之間如何進行賦值,并且能夠實現賦值操作符。對于任何賦值操作符,都應注意以下幾點:
(1)確保對象沒有自我賦值(如a = a)。
(2)復用被賦值對象中的資源或銷毀它。
(3)從源對象中將待復制內容復制到目的對象。
(4)最后,返回對目的對象的引用。
賦值操作符已在類中聲明。TIntStack類的賦值操作符簽名如下:
TIntStack& operator=(TIntStack& source) // ① TIntStack& operator=(const TIntStack& source) // ②``` 下面是賦值操作符的實現: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; // 見下文解釋
}`
分析:賦值操作由程序員顯式完成,系統(編譯器)絕不會直接調用它。賦值是在兩個現有對象之間執行的操作。如下語句:
`
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程序)
TIntStack a(5); // 自動對象(_auto object_),在退出main時被銷毀 TIntStack b(20); // 另一個自動對象``` 最后,我們返回對目的對象(也就是棧a)的引用。你可能會質疑,為什么要這樣做?假設我們編寫了一個級聯賦值操作(cascaded assignment operation)如下:`c = a = b; // 將b賦值給a,然后將結果再賦值給c` 賦值操作按從右到左的順序執行1,此表達式即轉換為:`c = (a = b); // 首先將b賦值給a,然后將結果賦值給c` 警告: 既然可以寫“a = b;”,那么也可以寫`a = a; // 語法上正確,但是邏輯上錯誤!` 這樣的表達式可能會通過指針和引用(別名)直接或間接地發生,我們的實現必須檢查是否出現這種情況。因此,必須核實源地址和目的地址是否相同。如果賦值發生在相同的對象之間,則不會進行任何操作,只返回對目的對象的引用。這就是上面示例的實現中,進行自我賦值檢查所完成的工作。如果沒有進行自我賦值檢查,可能會導致嚴重問題。考慮以下包含指針數據成員的TString類: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;`
以上operator=的實現并不安全,其中的①,刪除的是x1._data。接著在②中,我們又試圖使用assign._data。因為左側的對象(*this)與右側的對象(assign)是同一個對象,即assign._data與x1._data相同。所以,我們搬起石頭砸自己的腳。當執行②時,該程序可能崩潰,或者在別的地方出現問題。因此,在賦值操作的實現中,必須進行自我賦值檢查。
圖3-4
將b賦值給a非常簡單,之前的示例中已介紹過賦值如何工作。我們希望將賦值(b賦值給a)的結果再賦值給c。為使其正常工作,第1個賦值操作應返回a的值(或對a的引用)。否則,從a到c的第2次賦值就會出問題。通常,在C和C++中,我們只知道使用賦值操作的結果,卻并不了解內部的實現。例如,正是因為賦值操作的副作用,才使得以下代碼可以正常工作:
int i = 10, j = 20; // 此處無賦值操作 int k; cout << (i = j) << endl; // 將j賦值給i,并打印賦值結果①。if (k = j) { // 將j賦值給k,如果結果不為0,執行if體內部的語句②。// 省略此處代碼 }``` 在①中,完成賦值后返回i的值,并打印該值。與此類似,在②中,將j賦值給k,完成賦值后,返回k的值并與0進行比較。為了讓以上賦值都能正常工作,必須確保在賦值操作完成后,該賦值操作返回對a對象的引用。`Pascal:`在類似Pacal的語言中,以下的賦值操作沒有副作用:`a := b;` 在賦值后無法返回a的值。因此,以下語句`c := a := b;` 無法在Pascal(以及類似的語言,如Modula-2)中使用。思考:對比復制構造函數和賦值操作符中的代碼,會發現很多相似的地方。它們都將現有對象復制到另一個對象中。但是,你能否發現兩處代碼的主要差別?換言之,復制構造函數和賦值操作符的代碼有何區別?能否讓它們共享實現?為完成TIngStack的示例,其余的成員函數實現如下: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章中介紹。在本章討論中,只需記住賦值從右向左結合。
本文僅用于學習和交流目的,不代表異步社區觀點。非商業轉載請注明作譯者、出處,并保留本文的原始鏈接。
總結
以上是生活随笔為你收集整理的《C++面向对象高效编程(第2版)》——3.4 赋值操作符的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《HTML5触摸界面设计与开发》——导读
- 下一篇: 扎克伯格预言即将成真:计算机可解读图片内