指向类成员变量的指针并非指针

http://blog.csdn.net/oowgsoo/archive/2007/03/19/1533827.aspx

参考<<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对象的指针,成员变量的偏移量也没有错误
但是是否一定要这样做呢?这取决于程序员自己的选择 

参考<<C++必知必会>>的相关章节
"指向类成员函数的指针",这个术语中包含了"类成员函数"的术语,但是严格的说,这里的成员函数只是指非静态成员函数,这个术语中还包含了"指针"这个术语,
但是严格的说,它即不包含地址,行为也不象指针,说得干脆点,那就是"指向类成员函数的指针"并非指针.尽管这个术语有很大的迷惑性,但是就其含义来说,
可以把一组同类型的函数抽象为一个"指向函数的指针",同样的道理,可以把一组类中同类型的类成员函数抽象为一个"指向类成员函数的指针",两者是一致的
"指向类成员函数的指针"和"指向函数的指针"有什么区别?从字面上就可以清楚的知道,前者是和类,对象相关的,而后者直接指向函数的地址

我们首先复习一下"指向函数的指针"如何使用?
void print()
{
}

 void (*pfun)(); //声明一个指向函数的指针,函数的参数是void,函数的返回值是void
 pfun = print;  //赋值一个指向函数的指针
 (*pfun)();   //使用一个指向函数的指针

比较简单,不是吗?为什么*pfun需要用()扩起来呢?因为*的运算符优先级比()低,如果不用()就成了*(pfun())

"指向类成员函数的指针"比"指向函数的指针"就多了个类的区别:

struct CPoint
{
 void plus(double x_, double y_)
 {
 }
 void minus(double x_, double y_)
 {
 }
 void mul(double x_, double y_)
 {
 }
 void dev(double x_, double y_)
 {
 }

 virtual void move(double x_, double y_)
 {

 }
 double x;
 double y;
};

void Oper(CPoint* pPoint, void (CPoint::*pfun)(double x_, double y_), int x, int y)
{
 (pPoint->*pfun)(x, y);
}

struct CPoint3d : public CPoint
{
 void move(double x_, double y_)
 {

 }
};

int main(int argc, char* argv[])
{
 CPoint pt;
 void (CPoint::*pfun)(double x_, double y_);
 int offset = 0;

 pfun = &CPoint::plus;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);

 pfun = &CPoint::minus;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);
 
 pfun = &CPoint::move;
 offset = (int&)pfun;
 (pt.*pfun)(10, 10);
 Oper(&pt, pfun, 10, 10);

 CPoint3d pt3d;
 void (CPoint3d::*p3dfun)(double x_, double y_);
 p3dfun = &CPoint3d::move;
 (pt3d.*p3dfun)(10, 10);

 //p3dfun = pfun; //正确
 //pfun = p3dfun; //错误
 pfun = (void (CPoint::*)(double, double))p3dfun;

 Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
 
 return 0;
}

 void (CPoint::*pfun)(double x_, double y_);
这里是"指向类成员函数的指针"的声明,就是多了CPoint::的限定
 pfun = &CPoint::plus;
这里是"指向类成员函数的指针"的赋值,在赋值的时候必须用这种静态的方式
 (pt.*pfun)(10, 10);
这里是"指向类成员函数的指针"的使用,记住,解引用必须有实际的this指针地址,因此必须用有地址的对象pt来解引用,.*的语法有些怪异,不过我宁愿把它拆解为pt.和*pfun两部分来理解
 offset = (int&)pfun;
这里offset=4198410,当然不同的项目,不同的编译器这个值是不同的,由此也可以知道,"指向类成员函数的指针"确实是一个指针,其实由C++对象模型我们就应该知道这个结论了
,在C++对象模型中,成员函数是全局的,并不属于对象
有人想用这个值吗?或许可以用下面的代码:
 void (CPoint::*pfun2)(double x_, double y_);
 memcpy(&pfun2, &offset, sizeof(int));
 Oper(&pt, pfun2, 10, 10);
不过,我还是忍不住奉劝各位,尽量不要直接使用这个值,这毕竟是编译器内部实现的细节,实在有太多的人喜欢这种黑客似的代码并四处炫耀,真正的"指向类成员函数的指针"
的用法只应该包括声明,赋值和解引用

 pfun = &CPoint::move;
注意到这里的move是虚函数,那么这里还支持虚函数的多态吗?没有问题,"指向类成员函数的指针"支持多态,当然了,代价是,这时候这个指针就必须扩展为一个结构了,C++为了
"指向类成员函数的指针"支持多态是要付出代价的

 p3dfun = pfun; //正确
存在基类的"指向类成员函数的指针"到派生类的"指向类成员函数的指针"的隐式转换,其含义无疑是说基类的成员函数布局信息只是派生类中成员函数布局信息的一个子集,
因此这样的转换应该是没有问题的,但是反过来呢?
 //pfun = p3dfun; //错误
不存在派生类的"指向类成员函数的指针"到基类的"指向类成员函数的指针"的隐式转换,因为派生类中的成员函数并不一定能够在基类中找到
"指向类成员函数的指针"基类和派生类的关系和"指向类对象的指针"基类和派生类的关系完全相反,就"指向类成员函数的指针"的本质来说,这是合理的,但是这样的话,
我们就无法利用公共的Oper函数了,除非...

 pfun = (void (CPoint::*)(double, double))p3dfun; //强制转换
我们做强制转换是可以的
 Oper(&pt3d, (void (CPoint::*)(double, double))p3dfun, 10, 10);
而且也只有强制转换才可以利用公共的Oper函数了,这里的Oper调用的是pt3d中的move函数,没有错误的
但是是否一定要这样做呢?这取决于程序员自己的选择


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值