成员函数是类的一部分,其定义与普通函数一样,没有特殊之处。但是需要注意以下一些特点:
- 编译器隐式的将在类内部定义的成员函数当做内联函数(不管有没有使用inline关键字)。
- 在成员函数的声明或定义处指定inline关键字都是合法的。
- 类的成员函数可以访问该类的private成员。
- 使用类的对象来调用类的成员函数,如果是静态成员函数,还可以使用类名加::符号来调用。
- 成员函数可以重载。
介绍完成员函数的基本概念,我们就来开始今天的正文:const成员函数和this指针以及mutable关键字。
1. this指针
每个成员函数(除static成员函数)都有一个额外的、隐含的形参this。在调用成员函数时,形参this初始化为调用函数的对象的地址,所以形参this是指向类对象的指针。因为static成员函数是类的组成部分,但不属于任何一个类对象,所以static成员函数没有this指针。在成员函数中,不必显式的使用this指针来访问被调用函数所属对象的成员。对这个类的成员的任何没有前缀的引用,都被假定为通过指针this实现的引用。看个例子:
class A { public: bool is_same(const A &inst) const { return m_id == inst.m_id; } bool is_same_ext(const A &inst) const { return this->m_id == inst.m_id; } private: int m_id; };
该例子中,is_same和is_same_ext其实是一模一样的。
NB:由于this指针是隐式定义的,因此不需要也不能在函数的形参表中包含this指针,但可以在函数体中显式的使用this指针。
那this指针有什么用呢?这里举个例子,看下面代码:我们定义了一个Screen类,其中move成员函数用于将光标移动到某个位置,set函数用于设置光标处的值。我们分别定义了两个版本。
class Screen { public: typedef std::string::size_type index; public: void move_dumm(index r, index c); void set_dumm(char); Screen& move(index r, index c); Screen& set(char); private: std::string contents; index cursor; index height, width; }; /* 实现 */ void Screen::set_dumm(char c) { contents[cursor] = c; } Screen& Screen::set(char c) { contents[cursor] = c; return *this; } void Screen::move_dumm(index r, index c) { index row = r * width; cursor = row + c; } Screen& Screen::move(index r, index c) { index row = r * width; cursor = row + c; return *this; }
我们对比以下上面代码中的set与set_dumm,move和move_dumm,其实他们实现的功能是一样的,但是不带dumm后缀的都返回了this的引用,这样有什么好处呢?好处就是我们可以将一些操作连接成一个序列,在一个表达式中实现:
Screen myScreen; myScreen.move(4, 0).set('#'); //先将光标移到(4,0)位置,再将该处的值设置为'#'
其实上面这个语句等价于:
myScreen.move(4, 0); myScreen.set('#'); 或者 myScreen.move_dumm(4, 0); myScreen.set_dumm('#');
显然,不带dumm后缀的实现比不带的用起来更灵活。当然this指针的用途不止于此,这里不再介绍。
2. const成员函数
我们看到,上面举的第一个例子中的两个成员函数后面都加了const,这种成员函数叫const成员函数(也称为常量成员函数。没有const的成为为普通成员函数或非const成员函数。),而那个const其实是修饰this形参的。这里有两个非常重要的区别:
- 在普通的非const成员函数中,this的类型是一个指向类类型的const指针(
指向const对象的指针,指针本身不能再改变,即指针初始化时指向的是什么就是什么,后面不能再指向其他对象),也就是说我们可以改变const所指向的值,但是不能改变this所保存的地址。这是非常合理的,也就是说我们可以通过this指针修改类的某些成员的值,但我们不能改变this指向该对象的特性,否则就失去了this的含义。 - 在const成员函数中,this的类型是一个指向const类类型对象的const指针,即我们既不能改变this所指向的对象(不论该对象是否是const的),也不能改变this所保存的地址。不能从const成员函数返回指向类对象的普通引用,const成员函数只能返回*this作为一个const引用。
另外,需要注意两点:
- const对象、指向const对象的指针或引用只能用于调用其const成员函数,不能调用非const成员函数。
- 非const对象、非指向const对象的指针或引用既可以调用const成员函数,也可以调用非const成员函数。
上面的东西非常的抽象,我们看个例子:继续前面的例子,现在我们定义了一个display常量成员函数,用于在给定的ostream上打印屏幕的内容contens。因为打印contents不会改变对象,所以我们将display函数定义为const。这样根据前面const成员函数的定义,display内部的this指针将是一个const Screen*型的const,同时,它返回的类型也必须是const Screen&,正如下面代码中实现的那样。代码如下:
class Screen { public: typedef std::string::size_type index; public: Screen& move(index r, index c); Screen& set(char); const Screen& display(std::ostream &os) const { do_display(os); return *this; } private: void do_display(std::ostream &os) const { os << contents; } private: std::string contents; index cursor; index height, width; };
但这样定义是有问题的,比如下面的使用:
myScreen.display(cout).set('#');
这样使用是错误的,原因在于这个表达式实在由display返回的对象上运行set。但对象是const的(因为display是const成员函数,只能将this指针作为const引用返回),我们不能在const对象上面调用非const成员函数set。那如何解决这个问题呢?
可以使用基于const的重载:我们定义两个display操作,一个是const,另一个不是const,基于成员函数是否为const,可以重载一个成员函数;同样的,基于一个指针形参是否指向const,也可以重载一个函数。const对象只能使用const成员,非const对象可以使用任一成员,但非const版本是一个更好的匹配。下面是代码实现:
class Screen { public: typedef std::string::size_type index; public: Screen& move(index r, index c); Screen& set(char); const Screen& display(std::ostream &os) const { do_display(os); return *this; } Screen& display(std::ostream &os) { do_display(os); return *this; } private: void do_display(std::ostream &os) const { os << contents; } private: std::string contents; index cursor; index height, width; };
我们只是再重载实现了一个非const版本的display,这样将display嵌入到长表达式中去调用就不会有问题了。
3. mutable关键字
我们有时可能希望可以在const成员函数中改变对象的数据成员,那么我们就可以在数据类型声明前增加mutable关键字,这样我们变将这个数据成员声明为了可变数据成员。可变数据成员永远都不能为const,甚至当它是const对象的成员时也是如此。比如我们给Screen添加了一个新的可变数据成员access_ctr,用来跟踪调用Screen成员函数的频繁程度:
/*之前代码省略 */ private: void do_display(std::ostream &os) const { ++access_ctr; // 等效于:++this->access_ctr os << contents; } private: mutable size_t access_ctr; /* 之后代码省略*/
虽然do_display是const成员函数,但我们仍然可以在它里面改变数据成员access_ctr的值。
个人观点:虽然C和C++中引入const带来了许多方便和增强了许多功能,但不得不说,const也带来了许多混乱。
嗯嗯,你提醒的对。const指针不是指向const对象的指针,而是说指针本身不能再被改变(即不能再指向其他对象)。谢谢提醒~
嗯嗯,你提醒的对。const指针不是指向const对象的指针,而是说指针本身不能再被改变(即不能再指向其他对象)。谢谢提醒~