本節書摘來自異步社群出版社《c++面向對象高效程式設計(第2版)》一書中的第4章,第4.6節,作者: 【美】kayshav dattatri,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
c++面向對象高效程式設計(第2版)
指派與複制的操作非常類似。在c++中,絕大多數的複制操作都由語言隐式調用(當對象按值傳遞或按值傳回時)。當通過現有對象建立新對象時,也進行了複制操作(但不是很頻繁)。與複制相反的是,指派是必須由程式員顯式調用的操作。然而,在eiffel和smalltalk中,指派和複制操作都由程式員顯式調用。這也是基于值的語言與基于引用的語言之間的差別。
在c++中,對于對象和基本類型指派都具有相同的含義。把基本類型變量指派給另一個(相容的)基本類型變量時,将複制變量中的值。例如:
tpoint2d p1;
tpoint2d p2(100, 200);
p1 = p2;<code>`</code>
将p2資料成員中的值複制給p1資料成員。這裡不會特别對待指針和引用,複制它們的方式和複制基本資料類型相同。這就是預設指派操作(default assignment operation)。指派相應成員的方法稱為逐個成員指派(memberwise assignment),它由指派操作符實作。
`
tpoint2d::operator=(const tpoint2d& source);`
注意,operator在c++中是保留字(reserved word)1。如果類并未聲明和實作該指派操作符,編譯器将自動生成一個。而且該生成的指派操作符(稱為預設指派操作符)執行逐個成員指派。由編譯器提供的預設指派操作符實作,類似這樣:
tperson& tperson::operator=(const tperson& source)
{
if (this == &source) // 自我指派檢查
return *this;
// 首先,複制(指派)所有基本資料;
this->_ssn = source._ssn;
// 接下來,需要複制name中的字元。
// 如果name中的空間充足,則隻需複制字元即可,
// 否則,删除name所指向的現有記憶體,然後配置設定新的記憶體塊。
// 最後,複制字元。
if (source._name != 0) { // 是否有任何複制?
int namelength = strlen(source._name);
int thisnamelength = (this->_name) ?
strlen(this->_name) : 0;
if (namelength <= thisnamelength) // 簡單的情況
strcpy(this->_name, source._name);
else { // 複雜的情況
delete [] this->_name;
name = new char[namelength + 1];
// +1,為放置0
}
}
else {
delete [] this->_name; this->_name = 0;
// 為address重複以上步驟
if (source._address != 0) {
int addresslength = strlen(source._address);
int thisaddrlength = (this->_address) ?
strlen(this->_address) : 0;
if (addresslength <= thisaddrlength) {
// 簡單的情況
strcpy(this->_address, source._address);
delete [] this->_address;
_address = new char[addresslength + 1];
// +1,為放置0。
strcpy ( this->_address, source._address);
delete [] this->_address; this->_address = 0;
return *this;
}<code>`</code>
我們剛才實作的指派操作符,就是将tperson類對象顯式指派給另一個tperson類對象時,所使用的指派操作符。但是,某些情況下需要将其他類型的資料指派給tperson類對象,可以通過在類中實作重載指派操作符,以接受不同類型的參數。在後續章節中将介紹相關的示例。還需注意的是,_birthdate資料成員不能被複制,因為它是const成員,不允許為其指派。這裡再次假設,一旦建立一個tperson類對象,這個人的出生日期在其生存期内便不能改變。這個限制作用于_birthdate上似乎有些嚴格,但是它用于闡明帶有const資料成員限制的複制和指派的目的。如果這個限制對于一些應用程式而言過于嚴格死闆,也可将_birthdate改為非const成員。
需要遵循的規則是:
hand 一定要為每個類實作指派操作符,不要依賴語言所生成的預設指派操作符。
感興趣的讀者可能注意到,生成的預設複制構造函數和指派操作符都是内聯函數(inline function)。欲了解c++中複制構造函數的内部細節,請參閱第13章内容。
注意:
鑒于這種情況,在c++中,很容易控制對象的指派和複制。如果我們希望禁止公有客戶和派生類複制對象,隻需将複制構造函數設定成private。對于指派操作符也一樣。還需注意,可以限制(而不是完全禁止)制作的副本數目。本章稍後将會解釋為什麼需要這種副本控制。
smalltalk:
了解smalltalk中的指派語義很有趣。該語言用<-操作符表示指派。例如,<code>a <</code>- b`意味着将b指派給a。對于簡單(或者基本)類型,指派所涉及的複制值和c++中一樣。但是,指派應用于對象時,行為則完全不同。在smalltalk中,每個對象都是對記憶體中其他對象的引用。鑒于此,對象指派實際上就是引用指派。如果a和b都是對象,且通過操作a <- b将b指派給a,則b所引用的對象通過名稱a獲得另一個引用。指派後,a和b都引用相同的對象。無需多說,無論a在指派之前所引用的是什麼,在指派後都不能通過a再通路它。注意,smalltalk不允許在函數内部對形參指派,這是該語言的一項限制。
eiffel:
eiffel在指派限制方面和smalltalk完全一樣。該語言用操作符:=表示指派(和pascal一樣)。同樣,簡單類型間的指派也指複制值,而對象(實際上是對象引用( object reference ))間的指派則意味着複制引用。和smalltalk一樣,eiffel也不允許對形參指派。
4.6.1 左值操作指派
左值就是可被修改的值(通常在指派操作符左側的名稱)。詳見第3章中對左值和右值的介紹。在c和c++中,預設指派語義會産生一個左值,這意味着可以進行級聯指派操作(即,<code>a = b = c</code>)。在smlltalk中也可以這樣。實際上,smalltalk中的每種方法都保證有傳回值,這是指派的有用副作用,利用它可以寫出清楚簡練的表達式。但是,eiffel卻不允許級聯指派,這和pascal一樣。
記住:
c++允許實作者定義複制對象的語義。
c++允許實作者定義指派的語義。
實作者可以在每個類的基礎上控制複制和指派語義。
1譯者注:現在,<code>operator</code>在c++中已成為關鍵字。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。