1 对象内存布局
class 是一种特殊的 struct
- class 与 struct 遵循相同的内存对齐规则
- class 中的成员函数与成员变量分开存放(每个对象有独立的成员变量,所有对象共享类中的成员函数)
编程实验:对象内存布局
// 35-1.cpp
#include<iostream>
using namespace std;
class A
{
int i;
int j;
char c;
double d;
public:
void print()
{
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
A a;
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
a.print();
B* p = reinterpret_cast<B*>(&a); // 用于指针类型间的强制转换
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3.5;
a.print();
return 0;
}
- 类中成员函数与成员变量分开存放,使用 sizeof 求解的是成员变量的内存大小。class 与 struct 遵循相同的内存对齐规则。根据内存对齐规则,A 和 B 的大小都是 24 字节。
- 将结构体 B 类型的指针指向类对象 a,重新解释内存中的数据。类中的数据和结构体中数据的分布是完全一样的,所以我们可以通过结构体指针修改类对象中的数据。
编译运行:
$ g++ 35-1.cpp -o 35-1
$ ./35-1
sizeof(A) = 24
sizeof(B) = 24
i = -879428416, j = 21866, c = �, d = 6.95316e-310
i = 1, j = 2, c = c, d = 3.5
运行时的对象退化为结构体的形式,可以通过内存地址直接访问成员变量,访问权限关键字在运行时失效,只在编译时有效。
2 调用成员函数本质
- 类的成员函数位于代码段中
- 调用成员函数时对象地址作为参数隐式传递,成员函数通过对象地址访问成员变量
- C++ 语法规则隐藏了对象地址的传递过程
下面我们通过编程实验证明,当调用成员函数时对象地址作为参数隐式传递,成员函数通过对象地址访问成员变量
先看一段简单的代码
// 35-2.cpp
#include<iostream>
using namespace std;
class Demo
{
public:
Demo(int i, int j) : mi(i), mj(j) {}
int getI() { return mi; }
int getJ() { return mj; }
int add(int value)
{
return mi + mj + value;
}
private:
int mi;
int mj;
};
int main()
{
Demo d(1, 2);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "d.getI() = " << d.getI() << endl;
cout << "d.getJ() = " << d.getJ() << endl;
cout << "d.add(3) = " << d.add(3) << endl;
return 0;
}
上面的代码很简单,就是对象访问成员函数,由于隐藏了对象地址的传递过程,我们看不到调用成员函数时隐式传递对象地址。
下面我们用 C 语言模拟上面的过程,显示的传递对象地址,以明白对象调用成员函数的过程。
// ClassDemo.h
#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
#endif
// ClassDemo.c
#include<malloc.h>
#include"ClassDemo.h"
struct ClassDemo
{
int mi;
int mj;
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if (ret != NULL)
{
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
int Demo_Add(Demo* pThis, int value)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
// 35-3.c
#include<stdio.h>
#include"ClassDemo.h"
int main()
{
Demo* d = Demo_Create(1, 2);
printf("d.mi = %d\n", Demo_GetI(d));
printf("d.mj = %d\n", Demo_GetJ(d));
printf("Add(3) = %d\n", Demo_Add(d, 3));
Demo_Free(d);
return 0;
}
函数 Demo_Create、Demo_GetI、Demo_GetJ、Demo_Add 和 Demo_Free 的参数是void 的类型的指针,通过强制类型转换为结构体的指针,就可以操作结构体的成员,这些函数相当于成员函数,这里我们需要显示的传递对象的地址。
C++ 语法规则隐藏了对象地址的传递过程,这里我们使用 C 语言显示的传递对象地址模拟 C++ 对调用成员函数。
$ gcc 35-3.c ClassDemo.c -o 35-3
$ ./35-3
d.mi = 1
d.mj = 2
Add(3) = 6
3 小结
1、C++中类对象在内存布局上和结构体相同
2、成员变量和成员函数分开存放
3、调用成员函数时对象地址作为参数隐式传递