在 C++ 中,-> 运算符有一个独特的“递归”特性。当你写下 entity->Print() 时,编译器会查看 entity 的类型:
- 如果 entity 是原生指针:它直接解引用并访问成员。
- 如果 entity 是一个对象(类实例):
- 编译器会调用你重载的 operator->()。
- 这个函数必须返回两样东西之一:要么是一个原生指针,要么是另一个重载了 -> 的对象。
- 编译器拿到返回的指针后,自动再次对它使用箭头操作,直到找到最终的成员。
箭头运算符的使用及重载#
#include <iostream>
#include <string>
class Entity
{
public:
int x;
public:
void Print() const
{
std::cout << "Hello!x=" << x << std::endl;
}
};
//创建这个类的对象时传入一个堆上的对象,
//可以使用堆对象,且堆对象会被自动释放
class ScopePtr
{
private:
Entity* m_Obj;
public:
ScopePtr(Entity* entity)
:m_Obj(entity)
{
}
~ScopePtr()
{
std::cout << "release m_Obj" << std::endl;
delete m_Obj;
}
Entity* GetObject()
{
return m_Obj;
}
};
int main()
{
Entity e;
e.Print();
//取消引用后用 .函数
Entity* ptr = &e;
Entity& entity = *ptr;
//entity.Print();
//(*ptr).Print();
ptr->x = 2;
ptr->Print();
{
ScopePtr entity = new Entity();
entity.GetObject()->Print();
}
std::cin.get();
}重载箭头运算符#
#include <iostream>
#include <string>
class Entity
{
public:
int x;
public:
void Print() const
{
std::cout << "Hello!x=" << x << std::endl;
}
};
//创建这个类的对象时传入一个堆上的对象,
//可以使用堆对象,且堆对象会被自动释放
class ScopePtr
{
private:
Entity* m_Obj;
public:
ScopePtr(Entity* entity)
:m_Obj(entity)
{
}
~ScopePtr()
{
std::cout << "release m_Obj" << std::endl;
delete m_Obj;
}
Entity* GetObject()
{
return m_Obj;
}
//1. 告诉编译器:“我把内部的指针给你用,但你只能
// 看,不能改指针指向的那个 Entity。”
//2. 右边的const:“这个函数是一个‘只读’函数,它不会修改
// ScopedPtr 对象内部的任何成员变量。”,而且,他的返回值
//不能调用非const函数
const Entity* operator->() const
{
return m_Obj;
}
};
int main()
{
Entity e;
e.Print();
//取消引用后用 .函数
Entity* ptr = &e;
Entity& entity = *ptr;
//entity.Print();
//(*ptr).Print();
ptr->x = 2;
ptr->Print();
{
//隐式转换,编译器会寻找匹配的构造函数
//等同于:ScopePtr entity(new Entity()); // 直接初始化
// 或者 ScopePtr entity = ScopePtr(new Entity()); // 显式转换
ScopePtr entity = new Entity();
entity.GetObject()->Print();
entity->Print();
}
std::cin.get();
}对于ScopePtr entity = new Entity();,编译器的逻辑
- 右侧 new Entity() 的结果是一个 Entity* 类型的指针。
- 左侧需要一个 ScopePtr 类型的对象。
- 编译器检查 ScopePtr 类,发现它正好有一个构造函数 ScopePtr(Entity* entity)。
- 编译器自动调用这个构造函数,将 new 出来的地址传进去,生成了一个临时的 ScopePtr 对象。
利用箭头运算符计算偏移量#
#include <iostream>
#include <string>
struct Vector3
{
float x, y, z;
};
int main()
{
std::cout << (Vector3*)0 << std::endl;
//内存访问报错
//std::cout << ((Vector3*)0)->z << std::endl;
//int offset = (int)&((Vector3*)3)->z;//3+8=11
//当编译器看到 & ( ptr->z ) 时,它会把这一整串代码看作一个
// 单一的定位指令,而不是两个连续的动作。编译器内部的操作逻辑是:
//第一步:算出 z 的内存地址应该是多少?(计算结果:ptr的地址 + z的偏移量)。
//第二步:既然外面套着 & ,那我就直接把刚才算出来的地址数值交出去。
int offset = (int)&((Vector3*)0)->z;
std::cout << offset << std::endl;
std::cin.get();
}