类成员指针存储的是成员在类中的字节偏移而非内存地址,必须绑定对象实例才能使用,不能解引用或算术运算,且与普通指针类型不兼容、不可隐式转换。

类成员指针不是普通指针的“变体”,它根本就不是地址指针——它存的是偏移量,不能单独解引用,必须配合对象实例才能用。
成员指针不存储内存地址,只存类内偏移
普通指针(如 int*、void*)直接保存目标变量或函数的内存地址,能直接解引用或跳转;而类成员指针(无论是 int Screen::* 还是 void (Screen::*)())在 x64 下通常占 16 字节,内部结构包含 ptr 和 adj 两部分,本质是编译期确定的“成员在类布局中的字节偏移”(对非虚函数)或更复杂的调整信息(含虚继承时)。它不指向任何运行时有效地址,脱离对象就毫无意义。
- 你不能对
pdata做*pdata或pdata + 1这类操作 -
sizeof(int Screen::*)在多数平台 ≠sizeof(void*),常见为 8 或 16 字节 - 它无法隐式转换为
void*,static_cast<void>(pdata)</void>编译失败
调用语法强制绑定对象:必须用 .* 或 ->*
普通函数指针调用是 pf(1, 2) 或 (*pf)(1, 2);成员函数指针则必须显式提供 this 上下文:
- 对对象:用
(obj.*funcPtr)(args)—— 注意括号不能省,obj.*funcPtr(args)是错的(会先算funcPtr(args)) - 对对象指针:用
(ptr->*funcPtr)(args) - 数据成员指针同理:
obj.*dataPtr或ptr->*dataPtr,不是*ptr->dataPtr
漏掉 .* 或写成 .* 前少括号,编译器报错通常是 “expected primary-expression before ‘.’ token”,容易误判为语法拼写问题。
静态成员不适用成员指针,该用普通指针
静态成员变量和静态成员函数属于类,不依赖对象实例,因此它们的地址就是真实内存地址:
-
static int Screen::s_count;→ 可用int* p = &Screen::s_count;(普通指针) -
static void Screen::log();→ 可用void (*fp)() = &Screen::log; - 若错误地声明
void (Screen::*sp)() = &Screen::log;,编译器会报错:cannot take address of static member function
混淆静态/非静态是新手高频失误点——看到 :: 就想套 Class::* 语法,但静态成员压根不需要 this,也不参与偏移计算。
不能跨继承体系赋值,类型严格匹配
基类成员指针不能直接赋给派生类成员指针,哪怕成员签名一致:
struct Base { int x; };
struct Derived : Base { int y; };
int Base::* bp = &Base::x;
int Derived::* dp = bp; // ❌ 编译错误:incompatible types
这是因为成员指针编码了类布局信息,派生类可能引入虚表、虚继承等导致偏移变化。即使单继承无虚函数,C++ 标准也禁止这种隐式转换。安全做法是用 reinterpret_cast(需确认布局兼容),或重构为模板/策略模式避开强转。
真正难调试的不是语法错误,而是成员指针在多态、虚继承、多重继承场景下偏移计算失效——它不像普通指针那样“只要地址对就能读”,而是“偏移对 + 对象布局对 + 类型声明对”三者缺一不可。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











