在 c++的const对象一文中已经介绍了const对象的属性,本文尝试从this指针的角度,再来分析const对象。
同样的示例,定义一个类,并将其实例化为const对象:
//1_const_obj.cpp
class Obj
{
private:
int pri_dat;
public:
int get_pri_dat()
{
return pri_dat;
}
};
int main(void)
{
const Obj obj;
obj.get_pri_dat();
return 0;
}
编译报错:
$ g++ 1_const_obj.cpp
1_const_obj.cpp: In function ‘int main()’:
1_const_obj.cpp:17:12: error: uninitialized const ‘obj’
1_const_obj.cpp:19:18: error: passing ‘const Obj’ as ‘this’ argument of ‘int Obj::get_pri_dat()’ discards qualifiers
大概是说obj对象没有初始化,函数get_pri_dat()不能被调用。
前文中指出,obj是常量(const)对象,不允许被修改,所以不能通过obj对象去调用非const的成员函数,因为编译器担心非const函数会做出修改const对象中的成员的举动。好像也是这么一回事。当然这是从表面解释上述报错的原因,再往深层次分析,就得涉及顶层const、底层const和this指针。
看下面定义:
int a1 = 5;
const int a2 = a1; //顶层const
char a3 = 5;
char * const p1 = &a3; //顶层const
const char *p2 = &a3; //底层const
const char * const p3 = &5; //底层const
const float a4 = 1.34;
float * const p4 = &a4; //顶层const,报错
const float *p5 = &a4; //底层const,正确
在c++ primer 5e中,顶层const被称为常量指针,底层const被称为指向常量的指针,而没有提及指针常量。而在网上大多数资料说的:
(1) 顶层const指的是const修饰的是指针指向的地址,即指针指向的地址值不可被改变,称为指针常量。
(2) 底层const指的是const修饰的是指针指向的地址上的内容,即该内容为不可变,称为常量指针(指向常量的指针)。
在这里我们不纠结常量指针和指针常量、指向常量的指针这3个概念,而直接以顶层/底层const替代。
常量对象只能被底层const指针指向,反过来,底层const指针却既可以指向常量对象,也可以指向非常量对象,指向非常量对象时,不可以通过该指针修改所指向的非常量对象,但是不妨碍其他修改非常量对象的方式:
char a3 = 5;
const char *p1 = &a3; //底层const
char* const p2 = &a3; //顶层const
a3 = 6; //正确
*p1 = 9; //错误
*p2 = 5; //正确
p2 = NULL; //错误
c++中的引用就是通过顶层const指针实现的(上面的p2指针),而c++类中的this指针则是通过顶层const指针实现的,即this中保存的地址不允许被改变。this指针是什么?看下面解释。
代码语句:
obj.get_pri_dat();
我们使用点操作符来调用obj对象的get_pri_dat()成员函数,实际上这是在替某个对象调用它。当我们调用一个成员函数时,用请求该函数的对象的地址初始化this。如上调用语句可以等价的认为:
Obj::get_pri_dat(&obj);
成员函数get_pri_dat()的原型近似为:
Obj::get_pri_dat(Obj* const this);
在成员函数内部,我们可以直接使用调用该函数对象的成员,无序通过成员访问运算符来做到这点,因为this指针所指的正是这个对象。任何对类成员的直接访问都被看做this指针的隐式引用,get_pri_dat()函数的实现等价于:
return this->pri_dat;
普通成员函数,如上的get_pri_dat(),其this指针类型是指向所在类类型的顶层const指针,虽然它是隐式存在的,但是仍旧遵循指针的赋值规则,即this指针不能指向一个const对象。在前面我们将obj定义为const对象,显然相悖,这也是我们不能在一个常量对象上调用普通成员函数的原因。解决办法是将this指针声明为底层const指针,形似为:
Obj::get_pri_dat(const Obj* const this);
然而this指针是隐式的,不会出现在参数列表中,所以c++的做法是允许将const关键字放在成员函数的参数列表之后,该const表明所在成员函数的this指针是一个底层const指针:
class Obj
{
//...
public:
int get_pri_dat() const
{
//...
}
};
this指针为底层const的函数,也称为常量成员函数。常量成员函数只能访问,不能修改所在对象的内容。
将get_pri_dat()成员函数声明为const属性之后,编译:
$ g++ 1_const_obj.cpp
1_const_obj.cpp: In function ‘int main()’:
1_const_obj.cpp:22:12: error: uninitialized const ‘obj’
还剩下报错obj对象没有初始化,在类中自定义构造函数后编译就通过了。这个具体原因在前文已经详细说明,不赘述。