類--隱含的this指標
引言:
在前面提到過,成員函數具有一個附加的隱含形參,即指向該類對象的一個指標。這個隱含形參命名為this,與調用成員函數的對象綁定在一起。成員函數不能定義this形參,而是有編譯器隱含地定義。成員函數可以顯式的使用this指標,但不是必須這麼做。
1、何時使用this指標
有一種情況下,我們必須顯式使用this指標:當需要將一個對象作為整體引用而不是引用對象的一個成員時。
比如在類Screen中定義兩個操作:set和move,可以使得使用者將這些操作的序列串連成一個單獨的運算式:
myScreen.move(4,0).set('#'); //其等價於 myScreen.move(4.0); myScreen.set('#');
2、返回*this
在單個運算式中調用move和 set操作時,每個操作必須返回一個引用,該引用指向執行操作的那個對象:
class Screen{public: Screen &move(index r,index c); Screen &set(char); Screen &set(index,index,char);};
這樣,每個函數都會返回調用自己的那個對象。使用this指標可以用來訪問該對象:
Screen &Screen::set(char c){ contents[cursor] = c; return *this;}Screen &Screen::move(index r,index c){ index row = r * width; cursor = row + c; return *this;}
3、從const成員返回*this
在普通的非const成員函數中,this的類型是一個指向類類型的const指標。可以改變this所指向的值,但不能改變this所儲存的地址。在const成員函數中,this的類型是一個指向const類類型對象的const指標。既不能改變this所指向的對象,也不能改變this所儲存的地址。
不能從const成員函數返回指向類對象的普通引用。const成 員函數只能返回*this作為一個 const引用。
我們可以給Screen類增加一個const成員函數:display操作。如果將display作為 Screen的 const成員,則 display內部的 this指標將是一個constScreen* 型的const。然而:
myScreen.move(4,0).set('#').display(cout);//OK myScreen.display().set('*');//Error
問題在於這個運算式是在由display返回的對象上運行set。該對象是const,因為display將其對象作為const返回。我們不能在const對象上調用set。
4、基於const的重載
為瞭解決以上問題,我們必須定義兩個display操作:一個是const,一個不是const。基於成員函數是否為const,可以重載一個成員函數;同樣的,基於一個指標形參是否指向const,也可以重載一個函數。非const對象可以使用任一成員,但非const版本是一個更好的匹配。
class Screen{public: //As before Screen &display(std::ostream &os) { do_display(os); return *this; } const Screen &display(std::ostream &os) const { do_display(os); return *this; }private: void do_display(std::ostream &os) const { os << contents; } //As before};
調用:
Screen myScreen(5,3); const Screen blank(5,3); myScreen.set('#').display(cout); //調用非const版本 blank.display(cout); //調用const版本
5、可變資料成員
有時,我們希望類的資料成員(甚至是在const成員函數中)可以修改。這可以通過將它們聲明為mutable來實現。
可變資料成員永遠都不能為const,甚至當它們是const對象的成員時也如此。因此,const成員函數可以改變mutable成員。
class Screen{public: //...private: mutable size_t access_ctr; //使用access_ctr來跟蹤Screen成員函數的調用頻度 void do_display(std::ostream &os) const { ++ access_ctr; //OK os << contents; }};
【建議:用於公用代碼的私人實用函數】
使用私人實用函數(如前面的do_display)的好處:
1)一般願望是避免在多個地方編寫同樣的代碼。
2)display操作預期會隨著類的演變而變得複雜。當涉及到的動作變得更複雜時,只在一處而不是兩處編寫這些動作有更顯著的意義。
3)很可能我們會希望在開發時給do_display增加調試資訊,這些調試資訊將會在代碼的最終成品版本中去掉。如果只需要改變一個do_display的定義來增加或刪除調試代碼,這樣做將更容易。4)這個額外的函數調用不需要涉及任何開銷。我們使do_display成為內聯的,所以調用do_display與將代碼直接放入display操作的運行時效能應該是相同的。
P379習題12.13
//1. in screen.h#ifndef SCREEN_H_INCLUDED#define SCREEN_H_INCLUDED#include <string>class Screen{public: typedef std::string::size_type index; Screen &move(index r,index c); Screen &set(char); Screen &set(index,index,char); char get() const { return contents[cursor]; } char get(index ht,index wd) const; index get_cursor() const; Screen &display(std::ostream &os) { do_display(os); return *this; } const Screen &display(std::ostream &os) const { do_display(os); return *this; } Screen():cursor(0),height(0),width(0){} Screen(index,index,const std::string &tmp);private: void do_display(std::ostream &os) const { os << contents; } std::string contents; index cursor; index height,width;};inline char Screen::get(index ht,index wd) const{ index row = width * ht; return contents[row + wd];}inline Screen::index Screen::get_cursor() const{ return cursor;}#endif // SCREEN_H_INCLUDED
//2. in screen.cpp#include "screen.h"Screen::Screen(index ht,index wd,const std::string &cntnts):height(ht),width(wd),cursor(0),contents(cntnts){}Screen &Screen::set(char c){ contents[cursor] = c; return *this;}Screen &Screen::set(index ht,index wd,char c){ index row = ht * width; contents[row + wd] = c; return *this;}Screen &Screen::move(index r,index c){ index row = r * width; cursor = row + c; return *this;}
//3. in main.cpp#include <iostream>#include "screen.h"using namespace std;int main(){ Screen myScreen(5,6,"aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\n"); myScreen.move(4,0).set('#').display(cout);}