参考<<C++必知必会>>的相关章节
"指向类成员变量的指针",这个术语中包含了"类成员变量"的术语,但是严格的说,这里的成员变量只是指非静态成员变量,这个术语中还包含了"指针"这个术语,
但是严格的说,它即不包含地址,行为也不象指针,说得干脆点,那就是"指向类成员变量的指针"并非指针.尽管这个术语有很大的迷惑性,但是就其含义来说,
可以把一组同类型的变量抽象为一个"指向变量的指针",同样的道理,可以把一组类中同类型的类成员变量抽象为一个"指向类成员变量的指针",两者是一致的
如果你已经熟悉常规指针的声明语法,那么声明一个"指向类成员变量的指针"并不复杂:
int *ip;//一个指向int变量的指针
int C::*ip;//一个指向C类中int成员变量的指针
你必须要做的就是多写一个classname::来限定这个指针到底指向哪个类
一个常规的指针包含一个地址.如果解引用该指针,就会得到该地址的对象:
int a = 12;
int *ip;
ip = &a;
*ip = 0;
a = *ip;
但是一个"指向类成员变量的指针"并不包含一个地址,简单点说,实际上它是一个成员变量在类中的偏移量.当然,严格点说,因为C++标准并为对"指向类成员变量的指针"如何实现
做任何规定,说"指向类成员变量的指针"是一个整数的偏移量就不是一定正确,但是,大多数编译器确实是这样做的.下面我们看看"指向类成员变量的指针"是如何使用的?
#include "stdafx.h"
struct CPoint
{
double x;
double y;
};
void Print(CPoint* point, double CPoint::* p)
{
printf("%f/n", point->*p);
}
int main(int argc, char* argv[])
{
CPoint pt;
pt.x = 10;
pt.y = 20;
double CPoint::* p = NULL;
p = &CPoint::x;
double x = pt.*p;
Print(&pt, p);
p = &CPoint::y;
double y = pt.*p;
Print(&pt, p);
int offset = (int&)p;
return 0;
}
double CPoint::* p = NULL;
这是"指向类成员变量的指针"的声明,只是多了一个CPoint::而已,这个指针指向CPoint类中double类型的成员变量
p = &CPoint::x;
这是"指向类成员变量的指针"的赋值,记住,它是一个偏移量而不是地址,因此必须用这种静态的写法,这里不能用有地址的对象pt来赋值
double x = pt.*p;
这是"指向类成员变量的指针"的解引用,记住,解引用必须有实际的地址,因为必须用有地址的对象pt来解引用,.*的语法有些怪异,不过我宁愿把它拆解为pt.和*p两部分来理解
printf("%f/n", point->*p);
这也是"指向类成员变量的指针"的解引用, 和.*同样的道理,如果我们有一个指向CPoint的指针,我们就必须使用->*来解引用,你也可以把->*拆解为point->和*p两部分来理解
int offset = (int&)p;
这里把"指向类成员变量的指针"直接转换为int,这里的offset=8,恰好是CPoint类中的成员变量y在类中的偏移量,验证了我们说的"指向类成员变量的指针"是一个偏移量的说法
不过,我还是忍不住奉劝各位,尽量不要直接使用这个偏移量,这毕竟是编译器内部实现的细节,实在有太多的人喜欢这种黑客似的代码并四处炫耀,真正的"指向类成员变量的指针"
的用法只应该包括声明,赋值和解引用
加上派生又如何?
#include "stdafx.h"
struct CPoint
{
double x;
double y;
};
struct CPoint3d : public CPoint
{
double z;
};
void Print(CPoint* point, double CPoint::* p)
{
printf("%f/n", point->*p);
}
int main(int argc, char* argv[])
{
CPoint pt;
pt.x = 10;
pt.y = 20;
double CPoint::* p = NULL;
p = &CPoint::x;
double x = pt.*p;
Print(&pt, p);
p = &CPoint::y;
double y = pt.*p;
Print(&pt, p);
int offset = (int&)p;
double CPoint3d::* p3d = NULL;
p3d = &CPoint3d::z;
offset = (int&)p3d;
p3d = p; //正确
//p = p3d; //错误
p = (double CPoint::*)p3d; //强制转换
CPoint3d pt3d;
pt3d.z = 30;
Print(&pt3d, (double CPoint::*)p3d);
return 0;
}
offset = (int&)p3d;
这里的offset=16,正是CPoint3d中的成员变量z在类中的偏移量,有没有人说offset=0的,重新去看看C++的对象模型
p3d = p; //正确
存在基类的"指向类成员变量的指针"到派生类的"指向类成员变量的指针"的隐式转换,其含义无疑是说基类的成员变量偏移量只是派生类中成员变量偏移量的一个子集,
因此这样的转换应该是没有问题的,但是反过来呢?
//p = p3d; //错误
不存在派生类的"指向类成员变量的指针"到基类的"指向类成员变量的指针"的隐式转换,因为派生类中的成员变量并不一定能够在基类中找到
"指向类成员变量的指针"基类和派生类的关系和"指向类对象的指针"基类和派生类的关系完全相反,就"指向类成员变量的指针"的本质来说,这是合理的,但是这样的话,
我们就无法利用公共的Print函数了,除非...
p = (double CPoint::*)p3d; //强制转换
我们做强制转换是可以的
CPoint3d pt3d;
pt3d.z = 30;
Print(&pt3d, (double CPoint::*)p3d);
而且也只有强制转换才可以利用公共的Print函数了,这里的Print打印出来的是30,没有错误的,因为我们传入的是指向CPoint3d对象的指针,成员变量的偏移量也没有错误
但是是否一定要这样做呢?这取决于程序员自己的选择