C++中指向成员的指针运算符(.* 和 ->*)用法说明

news/2024/11/15 6:17:10/

目录

  • 一 MSDN中使用说明
    • 1.1 语法
    • 1.2 备注
  • 二 一个使用案例

一 MSDN中使用说明

1.1 语法

expression .* expression //直接成员解除引用运算符
expression –>* expression //间接成员解除引用运算符

1.2 备注

  C++中指向成员的指针运算符(.* 和 ->*)返回表达式左侧所指定的对象的特定类成员的值。 右侧必须指定该类的成员。 下面的示例演示如何使用这些运算符:

// expre_Expressions_with_Pointer_Member_Operators.cpp
// compile with: /EHsc
#include <iostream>using namespace std;class Testpm {
public:void m_func1() { cout << "m_func1\n"; }int m_num;
};// Define derived types pmfn and pmd.
// These types are pointers to members m_func1() and
// m_num, respectively.
void (Testpm::*pmfn)() = &Testpm::m_func1;
int Testpm::*pmd = &Testpm::m_num;int main() {Testpm ATestpm;Testpm *pTestpm = new Testpm;// Access the member function(ATestpm.*pmfn)();(pTestpm->*pmfn)();   // Parentheses required since * binds// less tightly than the function call.// Access the member dataATestpm.*pmd = 1;pTestpm->*pmd = 2;cout  << ATestpm.*pmd << endl<< pTestpm->*pmd << endl;delete pTestpm;
}

输出为:

m_func1
m_func1
1
2

  在前面的示例中,指向成员的指针 pmfn 用于调用成员函数 m_func1。 另一个指向成员的指针 pmd 用于访问 m_num 成员。
  二元运算符 .* 将其第一操作数(必须是类类型的对象)与其第二操作数(必须是指向成员的指针类型)组合在一起。
  二元运算符 ->* 将其第一操作数(必须是指向类类型的对象的指针)与其第二操作数(必须是指向成员的指针类型)组合在一起。
  在包含 .* 运算符的表达式中,第一个操作数必须是第二个操作数中指定的成员的类类型,并且可由该操作数访问,或者必须是明确派生自该类并可供该类访问的可访问类型。
  在包含 –>* 运算符的表达式中,第一个操作数的类型必须是第二个操作数中指定的类型的“指向类类型的指针”类型,或者它必须是明确派生自该类的类型。
  下面的示例说明了指向成员的指针运算符的第一个操作数可以是第二个操作数指定的类型的派生类的对象。

// expre_Expressions_with_Pointer_Member_Operators2.cpp
// C2440 expected
class BaseClass {
public:BaseClass(); // Base class constructor.void Func1();
};// Declare a pointer to member function Func1.
void (BaseClass::*pmfnFunc1)() = &BaseClass::Func1;class Derived : public BaseClass {
public:Derived();  // Derived class constructor.void Func2();
};// Declare a pointer to member function Func2.
void (Derived::*pmfnFunc2)() = &Derived::Func2;int main() {BaseClass ABase;Derived ADerived;(ABase.*pmfnFunc1)();   // OK: defined for BaseClass.(ABase.*pmfnFunc2)();   // Error: cannot use base class to// access pointers to members of// derived classes.(ADerived.*pmfnFunc1)();   // OK: Derived is unambiguously// derived from BaseClass.(ADerived.*pmfnFunc2)();   // OK: defined for Derived.
}

  指向成员的指针运算符 .* 或 ->* 的结果是在指向成员的指针的声明中指定的类型的对象或函数。 因此,在前面的示例中,表达式 ADerived.*pmfnFunc1() 的结果是指向返回 void 的函数的指针。 如果第二操作数是左值,则此结果为左值。

二 一个使用案例

  以上MSDN的说明相对简单,并没有展示指向成员的指针运算符的真实威力。那么,.* 和 ->* 运算符到底有什么用?为什么不直接用 直接成员\间接成员 运算符呢?我理解是为了特定情况下,规避虚函数表的使用。
  我们知道,基类指针想要调用子类成员函数,一般需要将该成员函数定义为虚函数,通过在类中维护虚函数表,从而实现基类指针调用子类方法的效果。但是虚函数需要在每个类中维护一个虚函数表,大大增加了程序了空间复杂度。
  在某些特定的使用情境下,例如在MFC中,有众多的消息,如果在基类中将所有消息的响应函数都定义为虚函数,那么在每个派生类中都需要维护一个虚函数表,那么整个MFC程序的空间复杂度陡增。开发MFC框架的天才们并没有这样做,也就是并没有在基类中将消息响应函数定义为虚函数。而是利用了.* 和 ->* 运算符实现了类似虚函数的多态性。
  我们先看下面一个例程。

#include<iostream>
using namespace std;class CBase
{
public:void BasePrintMsg() {cout << "In Base class" << endl;}
};class CDerive :public CBase
{
public:CDerive() :m_iDerive(3) {}int GetInt(int m) {return m_iDerive * m;}double GetDouble(int m, double d) {return m * d;}
private:int m_iDerive;
};typedef void (CBase::* pBaseVoidFun)();union UMapFuns
{//请注意,以下三个成员都是基类的成员函数的函数指针pBaseVoidFun pfn;double (CBase::*pfn_double)(int m, double d);int (CBase::*pfn_int)(int m);//请注意,下面是派生类的函数指针double (CDerive::*pfn_double_derive)(int m, double d);
};int main()
{CDerive cDeriveObj;CBase* pBase = &cDeriveObj;UMapFuns uMapFun;//调用成功,关键要理解uMapFun.pfn_int的地址实际上是子类成员函数的地址,子类对象//调用成员解除引用运算符时,不过是将子类对象的地址压栈,让子类成员函数能够根据这个//地址找到类的其他成员。uMapFun.pfn = (pBaseVoidFun)&CDerive::GetInt;int i1 = (cDeriveObj.*(uMapFun.pfn_int))(3);	//调用成功,用基类指针调用子类函数。关键要理解uMapFun.pfn_int的地址实际上是子类//成员函数的地址,基类指针此时的地址就是子类对象的地址,该地址压栈后,//子类成员函数根据这个地址找到的类的其他成员都是正确的。int i2 = (pBase->*(uMapFun.pfn_int))(4);uMapFun.pfn = (pBaseVoidFun)&CDerive::GetDouble;double d = (pBase->*(uMapFun.pfn_double))(3, 4.0);	//调用成功,用基类指针调用子类函数//调用失败,因为->*或者.*的第一个操作符必须是第二个操作符所属类或者其派生类的指针(对象)//double d2 = (pBase->*(uMapFun.pfn_double_derive))(3, 4.0);return 0;
}

  请注意思考上面的这行代码

int i2 = (pBase->*(uMapFun.pfn_int))(4);

  这是个令人动容的时刻,从代码看,我们竟然用基类指针调用了派生类的成员函数!为什么可以这样做呢?
  首先,派生类的成员函数指针可以显式转为基类的成员函数指针(哪怕基类中并没有这个成员函数,我们仅仅是定义了这样一种函数指针类型,如同联合体UMapFuns所作的一样)。其次,基类指针(请注意指针此时指向派生类对象)使用间接成员解除引用运算符时,实际上流程会转到这个函数指针所指向的地址(也就是子类成员函数中),而在调用这个这个子类对象的成员函数时,会把基类的this指针同时压栈;而这个this指针指向的地址实际上不仅包含基类对象,还包含子类对象!子类成员函数根据这个地址可以正确找到相应的类成员。
  在MFC中,正是利用了上述原理,将子类实现的所有消息响应函数集合为一个列表,接收消息以后,在子类中查找是否有对应的消息响应函数;一旦找到,就通过基类指针调用(当然是通过.* 和 ->*)这个子类的消息响应函数,从而实现由下向上(从子类到基类的方向)处理消息的目的。


http://www.ppmy.cn/news/1143461.html

相关文章

R实现地图相关图形绘制

大家好&#xff0c;我是带我去滑雪&#xff01; 地图相关图形绘制具有许多优点&#xff0c;这些优点使其在各种领域和应用中非常有用。例如&#xff1a;地图相关图形提供了一种直观的方式来可视化数据&#xff0c;使数据更容易理解和分析。通过地图&#xff0c;可以看到数据的空…

ThreeJS-3D教学七-交互

在threejs中想要选中一个物体&#xff0c;点击或者鼠标悬浮&#xff0c;又或者移动端的touch事件&#xff0c;核心都是通过new THREE.Raycaster完成的。这里用到了一个概念&#xff0c;即我们点击时的 屏幕坐标 转换为 three中的3D坐标。 先看效果图&#xff1a; 代码是&#…

TCP相关面试题

TCP相关面试题 题目1 介绍一下TCP三次握手的过程 介绍TCP三次握手应该从3个方面进行回答&#xff0c;分别是数据包名称&#xff0c;客户端与服务端的状态变化&#xff0c;数据包的序号变化。而不能只是简单回答发送的数据包名称。 TCP三次握手的过程如下&#xff1a; 从数据…

【深蓝学院】手写VIO第2章--IMU传感器--作业

这次作业坑很多&#xff0c;作业说明的不清楚&#xff0c;摸索了很长时间才将此次作业完成&#xff0c;在这里进行记录。 1. T1 1.1 题干 1.2 解答 1.2.1 法1&#xff0c;ros related方法 不知道为什么我的launch不了&#xff0c;在imu_utils目录下面建立build后&#xff0…

redis实战-实现用户签到UV统计

BitMap功能演示 我们针对签到功能完全可以通过mysql来完成&#xff0c;比如说以下这张表 用户一次签到&#xff0c;就是一条记录&#xff0c;假如有1000万用户&#xff0c;平均每人每年签到次数为10次&#xff0c;则这张表一年的数据量为 1亿条 每签到一次需要使用&#xff08…

面向对象设计-UML六种箭头含义

目录 UML概述UML语义UML表示法 六种常用关系标识方法泛化实现依赖关联聚合组合 本文参考文章 https://blog.csdn.net/qq_25091281/article/details/123801862 UML概述 UML (Unified Modeling Language)为面向对象软件设计提供统一的、标准的、可视化的建模语言。适用于描述以…

学生用的台灯护眼的哪种比较好?精选适合学生用的护眼台灯

现代小孩的学习压力确实很大&#xff0c;已经不能和我们以往那种“半大自然化学习”相提并论啦&#xff0c;如今各种学习PAD、电脑网课&#xff0c;成堆的学习资料与作业&#xff0c;恐怕是从小学甚至学前就已经是常态了。而且在平时我们路过学校的时候应该也不难发现&#xff…

Java基础面试,ArrayList和LinkedList的区别

ArrayList 基于动态数组&#xff0c;是一个连续的内存存储&#xff0c;适合进行下标访问扩容机制是&#xff0c;超出长度的时候需要重新建立数组&#xff0c;然后将老数组的数组拷贝到新的数组中。插入元素的时候性能消耗大&#xff0c;因为要移动其他的元素 LinkedList 基于…