天天看點

《C++面向對象高效程式設計(第2版)》——4.6 對象指派的語義

本節書摘來自異步社群出版社《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&amp; source);`

注意,operator在c++中是保留字(reserved word)1。如果類并未聲明和實作該指派操作符,編譯器将自動生成一個。而且該生成的指派操作符(稱為預設指派操作符)執行逐個成員指派。由編譯器提供的預設指派操作符實作,類似這樣:

tperson&amp; tperson::operator=(const tperson&amp; source)

{

   if (this == &amp;source) // 自我指派檢查

    return *this;

    // 首先,複制(指派)所有基本資料;

   this-&gt;_ssn = source._ssn;

    // 接下來,需要複制name中的字元。

    // 如果name中的空間充足,則隻需複制字元即可,

    // 否則,删除name所指向的現有記憶體,然後配置設定新的記憶體塊。

    // 最後,複制字元。

   if (source._name != 0) {  // 是否有任何複制?

    int namelength = strlen(source._name);

    int thisnamelength = (this-&gt;_name) ?

        strlen(this-&gt;_name) : 0;    

    if (namelength &lt;= thisnamelength)  // 簡單的情況

     strcpy(this-&gt;_name, source._name);

    else {   // 複雜的情況

     delete [] this-&gt;_name;

     name = new char[namelength + 1];

      // +1,為放置0

    }

   }

   else {

    delete [] this-&gt;_name; this-&gt;_name = 0;

    // 為address重複以上步驟

   if (source._address != 0) {

    int addresslength = strlen(source._address);

    int thisaddrlength = (this-&gt;_address) ?

        strlen(this-&gt;_address) : 0;

    if (addresslength &lt;= thisaddrlength) {

      // 簡單的情況

     strcpy(this-&gt;_address, source._address);

     delete [] this-&gt;_address;

     _address = new char[addresslength + 1];

      // +1,為放置0。

     strcpy ( this-&gt;_address, source._address);

    delete [] this-&gt;_address; this-&gt;_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中的指派語義很有趣。該語言用&lt;-操作符表示指派。例如,<code>a &lt;</code>- b`意味着将b指派給a。對于簡單(或者基本)類型,指派所涉及的複制值和c++中一樣。但是,指派應用于對象時,行為則完全不同。在smalltalk中,每個對象都是對記憶體中其他對象的引用。鑒于此,對象指派實際上就是引用指派。如果a和b都是對象,且通過操作a &lt;- 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++中已成為關鍵字。

本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。

繼續閱讀