->运算符对于指针来说是很常用的,也很方便。
->运算符还可以用于获取结构体元素的偏移。
首先,偏移是什么?
struct Student{
int age;char sex;
}
我们定义一个结构体的指针Student *stu = new Student();
如下图:
对于开辟的Student的空间,其存储结构大致是这样的,stu指针指向这个空间的首地址(也就是第一个属性开始的地址)。
然后,如何找到后面的属性呢?
就是根据偏移。
首先,Student的首元素为int类型的,所以其第二个元素的偏移值就是sizeof(int) = 4字节; 所以,我们要找到第二个元素,就直接用Student的首地址(指针stu记录的地址)加上4,就可以得到第二个元素的地址了。(这里使用的是Student的指针,如果是直接创建对象也是类型的,变量的地址就是首地址)
获取属性的偏移值
在一些场景下,我们需要获取到数据的偏移值,然后通过其地址找到首地址或者其它的场景下要获取到偏移值。
这里我们使用->运算符获取属性的偏移值。 同样以上面的Student为例子。
(int)&(((Student*)0)->age); (0也可以用NULL和nullptr来代替) // 这条语句就可以获取到age在Student类中的偏移值
原理:
(Student*)0 : 表示的是,我们将Student这个类的地址,准确的来说是首地址,看做为0。
将0转化为Student*类型。
(int)&(((Student*)0)->age) : 表示的是,我们将0看做Student的首地址之后,使用->运算符去访问age属性,然后对其取地址,获得age的地址,然后转换为int类型方便查看。
因为现在Student的首地址为0,所以获得元素的地址都是前一个元素的类型的字节数+0,也就是其的偏移值。(首元素的为0)
看下面代码: 获取到Vector3中的三个元素的偏移值
struct Vector3 {
float x, y, z;
};
int main() {
int offset = (int)&(((Vector3*)0)->x); // 0可以替 换成NULL或者nullptr
std::cout << offset << std::endl; // 0
offset = (int)&(((Vector3*)0)->y); // 0可以替换成NULL或者nullptr
std::cout << offset << std::endl; // 4
offset = (int)&(((Vector3*)nullptr)->z); // 0可以替换成NULL或者nullptr
std::cout << offset << std::endl; // 8
}
问题: 0的地址不是禁止操作的吗?
上面的代码,我们将地址0转换为对应的类,然后去访问内部数据。
但是地址0,(NULL,或者nullptr)不是不允许操作的吗?其实禁止操作,我们只要不去对0这块地址进行赋值就好了,去访问其元素是允许的。
比如: (Vector3*)0 -> age; // 这条语句是允许的
*((int*)0); // 也是允许的
因为我们只是访问,并没有赋值