每個類對象都將維護自己的類數據成員的拷貝例如
int main() {
Screen myScreen( 3, 3 ), bufScreen;
myScreen.clear();
myScreen.move( 2, 2 );
myScreen.set( '*' );
myScreen.display();
bufScreen.reSize( 5, 5 );<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
bufScreen.display();
}
myScreen 有自己的_screen 、_height、 _width 和_cursor 數據成員。BufScreen 也有自己獨立的一組成員,仍是每個類成員函數只存在一份拷貝。myScreen 和bufScreen 都調用任何特定成員函數的同一份拷貝。我們在上節已經看到成員函數可以引用自己的類成員而無需使用成員訪問操作符。例如函數move()的定義如下
inline void Screen::move( int r, int c )
{
if ( checkRange( r, c ) ) // 無效位置?
{
int row = (r-1) * _width; // 行位置
_cursor = row + c - 1;
}
}
如果調用了對象myScreen 的函數move() ,那麼在move()中訪問的數據成員_width 和_cursor 是myScreen 的數據成員,如果調用了對象bufScreen 的函數move() ,則訪問的是bufScreen 的數據成員move(),操縱的數據成員_cursor ,怎樣被依次綁定到屬於myScreen ,bufScreen 的數據成員上呢,簡短地回答起來就是用this 指針。
每個類成員函數都含有一個指向被調用對象的指針,這個指針被稱爲this 。在非const成員函數中,它的類型是指向該類類型的指針。在const 成員函數中,是指向const 類類型的指針。而在volatile 成員函數中,是指向volatile 類類型的指針。例如在類Screen 的成員函數move()中,this 指針的類型是Screen* 。在類List 的非const 成員函數中,this 指針的類型是List*。
因爲this 指針指向要調用其成員函數的類對象,所以如果函數move()被對象myScreen調用,則this 指針指向對象myScreen 。類似地,如果函數move()被對象bufScreen調用,則this 指針指向對象bufScreen。 Move()操縱的數據成員、_cursor 依次被綁定在屬於myScreen和bufScreen 的數據成員上。
要想理解這一點,一種方法是看一看編譯器是怎樣實現this 指針的。爲支持this 指針
必須要應用兩個轉變:
1 改變類成員函數的定義。用額外的參數this 指針來定義每個成員函數,例如:
// 僞代碼, 說明編譯器對一個成員函數定義的展開形式
// 不是合法的 C++ 代碼
inline void move( Screen* this, int r, int c )
{
if ( checkRange( r, c ) )
{
int row = (r-1) * this->_width;
this->_cursor = row + c - 1;
}
}
在這個成員函數定義中顯式使用this 指針來訪問類數據成員_width 和_cursor。
2 改變每個類成員函數的調用加上一個額外的實參——被調用對象的地址例如
myScreen.move( 2, 2)
被轉化爲
move( &myScreen, 2, 2 )
程序員可以在成員函數定義中顯式地引用this 指針例如如下定義成員函數home()是合法的,儘管這樣做不是必要的。
inline void Screen::home()
{
this->_cursor = 0;
}
但是有一種情況下確實需要程序員顯式地引用this 指針,比如我們在前面定義的Screen成員函數copy(), 下一小節將給出一些示例。
<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />13.4.1何時使用this 指針
函數main()在對象myScreen 和bufScreen 上調用類Screen 的成員函數這樣的動作是在獨立的語句中,我們可以重新定義Screen 類的成員函數,這樣當多個成員函數應用到同一個Screen 對象上時我們可以將成員函數調用連接起來。例如在函數main()中的調用可以寫爲
int main() {
// ...
myScreen.clear().move(2,2).set('*').display();
bufScreen.reSize(5,5).display();
}
這看起來似乎符合操作Screen 對象的直觀方式,這裏用到的動作序列是先清Screen對象myScreen, 然後把光標移到位置(2,2), 接着再把該位置的字符設爲* ,最後顯示結果
成員訪問操作符點. 和箭頭-> 是左結合操作符,當看到這些操作符序列時執行的順序是從左到右,例如先調用的是myScreen.clear() ,然後是myScreen.move()等等。爲使myScreen.move()在myScreen.clear()之後被調用,clear()必須返回類對象myScreen, 成員函數clear()的定義必須返回被調用的類對象。正如我們已經看到的,在類成員函數內是通過this指針來訪問該類對象的。下面是clear()的實現:
// clear() 的聲明在類體內
// 它指定了缺省實參 bkground = '#'
Screen& Screen::clear( char bkground )
{ // 重置 cursor 以及清屏幕
_cursor = 0;
_screen.assign( // 賦給字符串
_screen.size(), // size() 個字符
bkground // 值都是 bkground
);
// 返回被調用的對象
return *this;
}
注意這個成員函數的返回類型是Screen&, 它表示該成員函數返回一個引用,指向它自己所屬類類型的對象。爲了允許在main()中連接Screen 的成員函數,成員函數move()和set()也需要作修改,它們的返回類型也必須從void 改變成Screen& 且必須在函數定義中返回*this,類似地Screen 成員函數display()必須重新實現如下
Screen& Screen::display()
{
typedef string::size_type idx_type;
for ( idx_type ix = 0; ix < _height; ++ix )
{ // 針對每一行
idx_type offset = _width * ix; // row position
for ( idx_type iy = 0; iy < _width; ++iy )
// 針對每一列, 輸出元素
cout << _screen[ offset + iy ];
cout << endl;
}
return *this;
}
Screen 的成員函數resize()必須如下實現
// reSize() 的聲明在類體內
// 它指定了缺省實參 bkground = '#'
Screen& Screen::reSize( int h, int w, char bkground )
{ // 把屏幕的大小設置到高度 h 和 寬度 w
// 記住屏幕的內容
string local(_screen);
// 替換 _screen 所引用的字符串
_screen.assign( // 賦給字符串
h * w, // h * w 個字符
bkground // 值都是 bkground
);
typedef string::size_type idx_type;
idx_type local_pos = 0;
// 把原來屏幕的內容拷貝到新的屏幕上
for ( idx_type ix = 0; ix < _height; ++ix )
{ // 每一行
idx_type offset = w * ix; // 行位置
for ( idx_type iy = 0; iy < _width; ++iy )
// 每一列, 賦以原來的值
_screen[ offset + iy ] = local[ local_pos++ ];
}
_height = h;
_width = w;
// _cursor 保持不變
return *this;
}
在成員函數中,this 指針的用處不全是返回該成員函數被應用的對象,在13.3 節介紹成員函數copy()時我們看到了this 指針的另一種用法
void Screen::copy( const Screen& sobj )
{
// 如果 Screen 對象與 sobj 是同一個對象
// 無需拷貝
if ( this != &sobj )
{
// 把 sobj 的值拷貝到 *this 中
}
}
該this 指針含有被調用的類對象的地址,如果sobj 指向的對象的地址與this 指針值相等,則sobj 和this 指向同一對象拷貝,動作是不必要的。我們會在14.7 節介紹拷貝賦值操作符時再次看到這種結構。