1.数据成员布局
(1)成员变量地址规律
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
//定义一个类
class MYACLS
{
public:
int m_i;
static int m_si; //声明不是定义
int m_j;
static int m_sj;
int m_k;
static int m_sk;
};
int MYACLS::m_sj = 0; //这才是定义;
int main()
{
MYACLS myobj;
cout << sizeof(myobj) << endl;
myobj.m_i = 2;
myobj.m_k = 8;
myobj.m_j = 5;
printf("myobj.m_i = %p\n", &myobj.m_i);
printf("myobj.m_j = %p\n", &myobj.m_j);
printf("myobj.m_k = %p\n", &myobj.m_k);
MYACLS *pmyobj = new MYACLS();
printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
printf("pmyobj->m_k = %p\n", &pmyobj->m_k);
}
运行结果:
普通成员变量的存储顺序 是按照在类中的定义顺序从上到下来的;
比较晚出现的成员变量在内存中有更高的地址;
类定义中pubic,private,protected的数量,不影响类对象的sizeof;
(2)字节对齐
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
//定义一个类
//#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
class MYACLS
{
public:
int m_i;
static int m_si; //声明不是定义
int m_j;
static int m_sj;
int m_k;
static int m_sk;
char m_c; //1字节
int m_n; //4字节
};
//#pragma pack() //取消指定对齐,恢复缺省对齐;
int MYACLS::m_sj = 0; //这才是定义;
int main()
{
MYACLS myobj;
cout << sizeof(myobj) << endl;
myobj.m_i = 2;
myobj.m_k = 8;
myobj.m_j = 5;
printf("myobj.m_i = %p\n", &myobj.m_i);
printf("myobj.m_j = %p\n", &myobj.m_j);
printf("myobj.m_k = %p\n", &myobj.m_k);
printf("myobj.m_c = %p\n", &myobj.m_c);
printf("myobj.m_n = %p\n", &myobj.m_n);
MYACLS *pmyobj = new MYACLS();
printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
printf("pmyobj->m_k = %p\n", &pmyobj->m_k);
printf("pmyobj->m_c = %p\n", &pmyobj->m_c);
printf("pmyobj->m_n = %p\n", &pmyobj->m_n);
}
运行结果:
某些因素会导致成员变量之间排列不连续,就是边界调整(字节对齐),调整的目的是提高效率,编译器自动调整;
调整:往成员之间填补一些字节,使用类对象的sizoef字节数凑成 一个4的整数倍,8的整数倍;
但为了统一个平台下编译器的字节对齐问题,如网络传输中字节不统一的问题,引入了新概念——一字节对齐,即不采用字节对齐方式,就是在类的定义前后加上下述代码
#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
……
#pragma pack() //取消指定对齐,恢复缺省对齐;
再次运行结果:
(3)打印成员变量的偏移值
成员变量偏移值,就是这个成员变量的地址,离对象首地址偏移多少;
代码:
//三种方法
#include <iostream>
#include <stdio.h>
using namespace std;
#define GET(A,m) (int)(&((A*)0)->m) //方法2
//定义一个类
#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
class MYACLS
{
public:
int m_i;
static int m_si; //声明不是定义
int m_j;
static int m_sj;
int m_k;
static int m_sk;
char m_c; //1字节
int m_n; //4字节
private:
int m_pria;
int m_prib;
public:
void printMemPoint()
{
cout << "打印成员变量偏移值------方法1------" << endl;
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
printf("MYACLS::m_k = %d\n", &MYACLS::m_k);
printf("MYACLS::m_c = %d\n", &MYACLS::m_c);
printf("MYACLS::m_n = %d\n", &MYACLS::m_n);
printf("MYACLS::m_pria = %d\n", &MYACLS::m_pria);
printf("MYACLS::m_prib = %d\n", &MYACLS::m_prib);
cout << "打印成员变量偏移值------方法2------" << endl;
cout << GET(MYACLS, m_prib) << endl;
}
public:
virtual void myfv() {}
};
#pragma pack() //取消指定对齐,恢复缺省对齐;
int MYACLS::m_sj = 0; //这才是定义;
int main()
{
MYACLS myobj;
cout << sizeof(myobj) << endl;
myobj.m_i = 2;
myobj.m_k = 8;
myobj.m_j = 5;
printf("myobj.m_i = %p\n", &myobj.m_i);
printf("myobj.m_j = %p\n", &myobj.m_j);
printf("myobj.m_k = %p\n", &myobj.m_k);
printf("myobj.m_c = %p\n", &myobj.m_c);
printf("myobj.m_n = %p\n", &myobj.m_n);
MYACLS *pmyobj = new MYACLS();
printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
printf("pmyobj->m_k = %p\n", &pmyobj->m_k);
printf("pmyobj->m_c = %p\n", &pmyobj->m_c);
printf("pmyobj->m_n = %p\n", &pmyobj->m_n);
pmyobj->printMemPoint();
cout << "打印成员变量偏移值------方法3------" << endl;
//成员变量指针
int MYACLS::*mypoint = &MYACLS::m_n;
printf("pmyobj->m_n偏移值 = %d\n", mypoint);
return 1;
}
运行结果 :
注:对象开始的数据成员为虚函数表指针
2.数据成员存取
(1)静态成员变量存取
静态成员变量,可以当做一个全局量,但是他只在类的空间内可见;引用时有三种方式
类名::静态成员变量
对象.静态成员变量
对象指针->静态成员变量
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
class MYACLS
{
public:
int m_i;
static int m_si; //声明而不是定义
int m_j;
};
int MYACLS::m_si = 10; //这个是定义
int main()
{
MYACLS myobj;
MYACLS *pmyobj = new MYACLS();
//对静态成员变量的3种调用方式
cout << MYACLS::m_si << endl;
cout << myobj.m_si << endl;
cout << pmyobj->m_si << endl;
//用三种调用方式给变量赋值
//三种方法编译成汇编语句没有任何区别
MYACLS::m_si = 1;
cout << MYACLS::m_si << endl;
myobj.m_si = 2;
cout << MYACLS::m_si << endl;
pmyobj->m_si = 3;
cout << MYACLS::m_si << endl;
//打印类中成员变量的地址
printf("myobj.m_i = %p\n", &myobj.m_i);
printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
printf("MYACLS::m_si = %p\n", &MYACLS::m_si);
printf("myobj.m_si = %p\n", &myobj.m_si);
printf("pmyobj->m_si = %p\n", &pmyobj->m_si);
return 1;
}
运行结果:
第一次:
第二次:
结论:静态成员变量只有一个实体,保存在可执行文件的数据段的,在编译时就会生成好这个可执行文件,所以每次执行地址都不会改变
(2)非静态成员变量存取
非静态成员变量的存取(普通的成员变量),存放在类的对象中,存取通过类对象(或类对象指针)
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
class FAC
{
public:
int m_fai;
int m_faj;
};
class MYACLS : public FAC
{
public:
int m_i;
static int m_si; //声明而不是定义
int m_j;
void myfunc()
{
m_i = 5;
m_j = 6;
}
};
int MYACLS::m_si = 10; //这个是定义
int main()
{
MYACLS myobj;
MYACLS *pmyobj = new MYACLS();
printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
pmyobj->myfunc();
//编译器角度:MYACLS::myfunc(pmyobj)
//MYACLS::myfunc(MYACLS *const this)
//{
//this->m_i = 5;
//this->m_j = 5;
//}
pmyobj->m_faj = 7;
myobj.m_i = 15;
pmyobj->m_i=13;
return 1;
}
反汇编:
对于普通成员的访问,编译器是把类对象的首地址加上成员变量的偏移值,比如 &myobj + 4 = &myobj.m_j;
可以发现对于普通类的成员,不管是 对象.成员变量 还是 对象指针->成员变量 访问效率差不多,但对于虚基类这两种访问方式有区别,见后面的博客;