类
--隐含的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); }