C++ this指针详解
this 是C++中的一个关键字,也是一个常量指针,指向当前对象(具体说是当前对象的首地址)。通过 this,可以访问当前对象的成员变量和成员函数。
所谓当前对象,就是正在使用的对象,例如对于stu.say();,stu 就是当前对象,系统正在访问 stu 的成员函数 say()。
假设 this 指向 stu 对象,那么下面的语句中,this 就和 pStu 的值相同:
1
2
|
Student stu; //通过Student类来创建对象
Student *pStu = &stu;
|
[示例] 通过 this 来访问成员变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Student{
private :
char *name;
int age;
float score;
public :
void setname( char *);
void setage( int );
void setscore( float );
};
void Student::setname( char *name){
this ->name = name;
}
void Student::setage( int age){
this ->age = age;
}
void Student::setscore( float score){
this ->score = score;
}
|
本例中,函数参数和成员变量重名是没有问题的,因为通过 this 访问的是成员变量,而没有 this 的变量是函数内部的局部变量。例如对于this->name = name;语句,赋值号左边是类的成员变量,右边是 setname 函数的局部变量,也就是参数。
下面是一个完整的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include <iostream>
using namespace std;
class Student{
private :
char *name;
int age;
float score;
public :
void setname( char *);
void setage( int );
void setscore( float );
void say();
};
void Student::setname( char *name){
this ->name = name;
}
void Student::setage( int age){
this ->age = age;
}
void Student::setscore( float score){
this ->score = score;
}
void Student::say(){
cout<< this ->name<< "的年龄是 " << this ->age<< ",成绩是 " << this ->score<<endl;
}
int main(){
Student stu1;
stu1.setname( "小明" );
stu1.setage(15);
stu1.setscore(90.5f);
stu1.say();
Student stu2;
stu2.setname( "李磊" );
stu2.setage(16);
stu2.setscore(80);
stu2.say();
return 0;
}
|
运行结果:
1
2
|
小明的年龄是 15,成绩是 90.5
李磊的年龄是 16,成绩是 80
|
对象和普通变量类似;每个对象都占用若干字节的内存,用来保存成员变量的值,不同对象占用的内存互不重叠,所以操作对象A不会影响对象B。
上例中,创建对象 stu1 时,this 指针就指向了 stu1 所在内存的首字节,它的值和 &stu1 是相同的;创建对象 stu2 时,this 等于 &stu2;创建对象 stu3 时也一样。
我们不妨来证明一下,给 Student 类添加一个成员函数,输出 this 的值,如下所示:
1
2
3
|
void Student::printThis(){
cout<< this <<endl;
}
|
然后在 main 函数中创建对象并调用 printThis:
1
2
3
4
5
6
|
Student stu1, *pStu1 = &stu1;
stu1.printThis();
cout<<pStu1<<endl;
Student stu2, *pStu2 = &stu2;
stu2.printThis();
cout<<pStu2<<endl;
|
运行结果:
1
2
3
4
|
0x28ff30
0x28ff30
0x28ff10
0x28ff10
|
可以发现,this 确实指向了当前对象的首地址,而且对于不同的对象,this 的值也不一样。
几点注意:
this 是常量指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
this 只能在成员函数内部使用,其他地方没有意义,也是非法的。
只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用,后续会讲到。
this 到底是什么
实际上,this 指针是作为函数的参数隐式传递的,它并不出现在参数列表中,调用成员函数时,系统自动获取当前对象的地址,赋值给 this,完成参数的传递,无需用户干预。
this 作为隐式参数,本质上是成员函数的局部变量,不占用对象的内存,只有在发生成员函数调用时才会给 this 赋值,函数调用结束后,this 被销毁。
正因为 this 是参数,表示对象首地址,所以只能在函数内部使用,并且对象被实例化以后才有意义。
C++常对象及其成员
C++虽然采取了不少有效的措施(如设private保护)以增加数据的安全性,但是有些数据却往往是共享的,人们可以在不同的场合通过不同的途径访问同一个数据对象。有时在无意之中的误操作会改变有关数据的状况,而这是人们所不希望出现的。
既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用const,即把有关的数据定义为常量。
常对象
在定义对象时指定对象为常对象。常对象必须要有初值,如:
1
|
Time const t1(12,34,46); //t1是常对象
|
这样,在所有的场合中,对象t1中的所有成员的值都不能被修改。凡希望保证数据成员不被改变的对象,可以声明为常对象。
定义常对象的一般形式为:
1
|
类名 const 对象名[(实参表列)];
|
也可以把const写在最左面:
1
|
const 类名 对象名[(实参表列)];
|
二者等价。
如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数和析构函数)。例如,对于例9.7中已定义的Time类,如果有
1
2
|
const Time t1(10,15,36); //定义常对象t1
t1.get_time( ); //企图调用常对象t1中的非const型成员函数,非法
|
这是为了防止这些函数会修改常对象中数据成员的值。
不能仅依靠编程者的细心来保证程序不出错,编译系统充分考虑到可能出现的情况,对不安全的因素予以拦截。现在,编译系统只检查函数的声明,只要发现调用了常对象的成员函数,而且该函数未被声明为const,就报错,提请编程者注意。
引用常对象中的数据成员很简单,只需将该成员函数声明为const即可。如:
1
|
void get_time( ) const ; //将函数声明为const
|
这表示get_time是一个const型函数,即常成员函数。
常成员函数可以访问常对象中的数据成员,但仍然不允许修改常对象中数据成员的值。有时在编程时有要求,一定要修改常对象中的某个数据成员的值,ANSI C++考虑到实际编程时的需要,对此作了特殊的处理,对该数据成员声明为mutable,如:
1
|
mutable int count;
|
把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。
常对象成员
可以将对象的成员声明为const,包括常数据成员和常成员函数。
1) 常数据成员
其作用和用法与一般常变量相似,用关键字const来声明常数据成员。常数据成员的值是不能改变的。
有一点要注意: 只能通过构造函数的参数初始化表对常数据成员进行初始化。如在类体中定义了常数据成员hour:
1
|
const int hour; //声明hour为常数据成员
|
不能采用在构造函数中对常数据成员赋初值的方法,下面的做法是非法的:
1
2
3
|
Time::Time( int h){
hour=h;
} // 非法
|
因为常数据成员是不能被赋值的。
在类外定义构造函数,应写成以下形式:
1
|
Time::Time( int h):hour(h){} //通过参数初始化表对常数据成员hour初始化
|
常对象的数据成员都是常数据成员,因此常对象的构造函数只能用参数初始化表对常数据成员进行初始化。
2) 常成员函数
前面已提到,一般的成员函数可以引用本类中的非const数据成员,也可以修改它们。如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们,例如只用于输出数据等。如
1
|
void get_time( ) const ; //注意const的位置在函数名和括号之后
|
const是函数类型的一部分,在声明函数和定义函数时都要有const关键字,在调用时不必加const。常成员函数可以引用const数据成员,也可以引用非const的数据成员。const数据成员可以被const成员函数引用,也可以被非const的成员函数引用。具体情况可以用下表表示。
那么怎样利用常成员函数呢?
如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。
如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证
如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,但应确保在函数中不修改对象中的数据成员。
不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。
还有一点要指出,常成员函数不能调用另一个非const成员函数。