下面程序中pb->func();的输出结果?
#include <iostream>
#include<stdio.h>
using namespace std;
class M
{
public:
M() {a = 1; b = 2;};
~M() {};
void func()
{
cout<<a<<" "<<b<<endl;
}
//private:
int a;
int b;
};
class N
{
public:
N() {c = 3;};
~N() {};
void func()
{
cout<<c<<endl;
}
//private:
int c;
};
int main()
{
M m;
N *pb = (N*) (&m); //!!!!!!!!!!!!
pb->func();
cout<<"################"<<endl;
cout<<&m<<endl;
cout<<(&m.a)<<endl;
cout<<pb<<endl;
cout<<"################"<<endl;
printf("%p\n", &M::a);//%p 格式转为指针,输出地址
printf("%p\n", &M::b);
printf("%p\n", &N::c);
cout<<"################"<<endl;
return 0;
}
结果:
1
################
0x28ff04
0x28ff04
0x28ff04
################
00000000
00000004
00000000
################
Process returned 0 (0x0) execution time : 0.173 s
Press any key to continue.
暂且不讨论该程序设计有多么糟糕,但程序主要考察关于类对象成员调用的机制,关于这方面,在《深入理解C++对象模型》中有详解。
这里主要涉及到两方面:一是对象调用成员函数时会将调用对象与函数绑定;二是对象访问成员是根据该成员距离对象的偏移量来访问的!!!!,而不是根据成员名来访问,所谓偏移量,就是告诉你一个特定的成员位置距离对象的起点有多少个字节。
上面程序,内存中实例化了一个M类对象,然后将该地址强制转换成一个N类地址,即将该对象的地址内容强制看成一个N类对象。pb为N类的指针,理所当然调用的是N类中的fun()函数(可以跟多态的情形相比较),当调用fun()函数时,调用对象与该函数进行绑定,即fun()函数中隐含的形参this指针初始化为调用对象(M类对象)的地址,假设为0xff80。然后fun()函数打印值c。这里要注意,对象在访问类成员时,编译器并没有存储该对象各个成员的实际地址,而是存储了其相对于当前对象首地址的偏移量,由于N类只有一个成员c,在编译阶段,编译器就记录了c对于N类对象的偏移量为0,故访问c时,便是访问当前对象地址this+偏移量0,注意,this在这里绑定的是M类对象的首地址,在M类中,偏移量为0的成员是a,故打印出a的值。
关于类成员偏移量的输出,可以用程序验证。
例如如下程序:将地址0强制转换为A类对象的地址,那么打印类成员m_a和m_b的地址便是他们的偏移量,如下分别输出0,0x4。
cout<<&((M*)0)->a<<endl;
cout<<&((M*)0)->b<<endl;
关于成员指针!!!!
成员指针只是记录一个成员的偏移量,而非地址,因为类中没有地址,选择一个类的成员只是意味着在类中偏移,只有把这个偏移和具体对象的首地址结合,才能得到实际地址。
成员指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员,最直接的理解是将其理解为一个偏移量。这个偏移量适用于某一类A的任何对象,换言之,如果一个A类对象的成员a距离起点的偏移量是4,那么任何其他A类对象中,a的偏移都是4字节。
啰嗦一大堆,总结一下就是两句话:
类对象访问其成员时,是根据该成员在类中的偏移量来访问的。
类成员指针,可以理解为指向类数据成员的一个偏移量,而非地址。