C++中指向成员的指针运算符(.* 和 ->*)用法说明
目录
- 一 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中,正是利用了上述原理,将子类实现的所有消息响应函数集合为一个列表,接收消息以后,在子类中查找是否有对应的消息响应函数;一旦找到,就通过基类指针调用(当然是通过.* 和 ->*)这个子类的消息响应函数,从而实现由下向上(从子类到基类的方向)处理消息的目的。
相关文章:
C++中指向成员的指针运算符(.* 和 ->*)用法说明
目录 一 MSDN中使用说明1.1 语法1.2 备注 二 一个使用案例 一 MSDN中使用说明 1.1 语法 expression .* expression //直接成员解除引用运算符 expression –>* expression //间接成员解除引用运算符 1.2 备注 C中指向成员的指针运算符(.* 和 ->*)…...
ASUS华硕ZenBook灵耀X逍遥UXF3000E_UX363EA原装出厂预装Win11系统工厂模式安装包
下载链接:https://pan.baidu.com/s/1WLPp0e5AZErtX3bJIhTZMg?pwd2j7i 带有ASUS Recovery恢复功能、自带所有驱动、出厂主题壁纸、Office办公软件、MyASUS华硕电脑管家等预装程序 所需要工具:16G或以上的U盘(非必需) 文件格式:HDI,SWP,OFS,E…...
【数据结构】栈和队列-- OJ
目录 一 用队列实现栈 二 用栈实现队列 三 设计循环队列 四 有效的括号 一 用队列实现栈 225. 用队列实现栈 - 力扣(LeetCode) typedef int QDataType; typedef struct QueueNode {struct QueueNode* next;QDataType data; }QNode;typedef struct …...
访问Apache Tomcat的管理页面
配置访问Tomcat管理页面的用户名、密码、角色 Tomcat安装完成后,包含了一个管理应用,默认安装在 <Tomcat安装目录>/webapps/manager 例如: 要使用管理页面的功能,需要在conf/tomcat-users.xml文件中配置用户、密码及角色…...
企业组织内如何避免山头文化?
1,什么是山头文化 2,山头文化的危害 3,如何避免山头文化 01什么是山头文化 山头文化就是指某一组织中的一部分人员组成一个以共同利益为基础的集体,就如同古代占山头一样,在组织中形成一股无形的力量,其…...
【c#】线程Monitor.Wait和Monitor.Pulse使用
介绍 以一个简易版的数据库连接池的实现来说明一下 连接池的connection以队列来管理 getConnection的时候,如果队列中connection个数小于50,且暂时无可用的connection(个数为0或者peek看下头部需要先出那个元素还处于不可用状态)…...
GitLab平台安装中经典安装语句含义解析
yum -y install policycoreutils openssh-server openssh-clients postfix 这是一个Linux命令,用于使用YUM包管理器安装指定的软件包。下面是对这个命令各部分的解释: yum:这是一个Linux命令行工具,用于管理RPM(Red …...
湘潭大学 2023年下学期《C语言》作业0x03-循环1 XTU OJ 1094,1095,1096,1112,1113
第一题 #include<stdio.h>int main() {int t;int count1;scanf("%d",&t);while(t--){int a,b,c;scanf("%d%d",&a,&b);cab;printf("Case %d: %d\n",count,c);count;}return 0; } 记住多样例输入的模板,熟悉计数器…...
【Linux系统满足产品实时性需求】
一、背景: 应用实时性:应用程序1以固定周期执行实时算法; 应用程序2以固定周期,执行串口收发; 驱动实时性:驱动sdio接口,实现与FPGA数据交互,实现串口数据收发。 二、实时性保证&…...
不用休眠的 Kotlin 并发:深入对比 delay() 和 sleep()
本文翻译自: https://blog.shreyaspatil.dev/sleepless-concurrency-delay-vs-threadsleep 毫无疑问,Kotlin 语言中的协程 Coroutine 极大地帮助了开发者更加容易地处理异步编程。该特性中封装的诸多高效 API,可以确保开发者花费更小的精力去…...
在Ubuntu中批量创建用户
一、背景知识 在Linux操作系统中创建新用户可以使用useradd或adduser命令。 使用useradd命令创建用户时,不会在/home目录下创建用户文件夹,需要用户自己指定主目录和bash目录的位置。同时,创建的用户没有设置密码,无法进行登录&a…...
汽车冲压车间的RFID技术设计解决方案
一、RFID技术的基本原理 RFID技术是一种利用非接触式自动识别的技术,通过将RFID标签放置在被识别物品上,并使用RFID读写器对标签进行扫描和识别,实现对物品的自动识别和追踪。RFID标签分为被动式和主动式两种。被动式标签无内置电源…...
TCP 和UDP通信流程
TCP 通信流程 根据上图可以看到,TCP 服务器和客户端通信分为 TCP 服务端和客户端,需要先建立服务 端然后再建立客户端与之连接进行数据交互。 服务端编程步骤: 1.使用 socket 创建流式套接字 2.使用 bind 绑定将服务器绑定到 IP 3.listen…...
Swift SwiftUI CoreData 过滤数据 1
Xcode: Version 14.3.1 (14E300c) iOS: 16 预览: Code: import SwiftUI import CoreDatastruct TodosSearch: View {State private var search_title "测试"FetchRequest var todos_search: FetchedResults<Todo>init() {let request: NSFetchReq…...
【uniapp】subnvue组件数据更新视图未更新问题
背景 : 页面中的弹窗使用了subnvue来写, 根据数据依次展示一个一个的弹窗, 点击"关闭"按钮关闭当前弹窗, 显示下一个弹窗 问题 : 当点击关闭时( 使用的splice() ), 数据更新了 , 而视图没有更新, 实际上splice() 是不仅更新数据, 也可以更新视图的 解决 : this.$fo…...
Unity编辑器拓展-Odin
1.相比于原生Unity的优势 Unity不支持泛型类型序列化,例如字典原生Unity不支持序列化,而Odin可以继承序列化的Mono实现功能强大且使用简单,原生Unity想实现一些常见的功能需要额外自己编写Unity扩展的编码,实现功能只需要加一个特…...
小红书婴童产业探索,解析消费者需求!
在消费升级、市场引导的背景下,众多产业都在悄然发生着变化,其中“婴童产业”就是非常有代表性的一个。今天就来深入分析小红书婴童产业探索,解析消费者需求! 一、何为婴童产业 事实上,婴童产业,并不仅仅局…...
离线安装mysql客户端
下载路径 oracle网站总是在不断更新,所以下载位置随时可能变动但万变不离其宗,学习也要学会一通百通。 首先直接搜索,就能找找到mysql官网 打开网站,并点击 DOWNLOADS 往下滚动,找到社区版下载按钮。…...
Docker 数据管理
管理 Docker 容器中数据主要有两种方式: 数据卷(Data Volumes) 数据卷容器(DataVolumes Containers)。 数据卷 数据卷是一个供容器使用的特殊目录,位于容器中。可将宿主机的目录挂载到数据卷上…...
数据统计--图形报表--ApacheEcharts技术 --苍穹外卖day10
Apache Echarts 营业额统计 重点:已完成订单金额要排除其他状态的金额 根据时间选择区间 设计vo用于后端向前端传输数据,dto用于后端接收前端发送的数据 GetMapping("/turnoverStatistics")ApiOperation("营业额统计")public Result<TurnoverReportVO…...
FPGA与CPU电源时序测试技术解析与实践
1. FPGA与CPU电源时序测试的核心挑战在现代电子系统中,FPGA、MCU和CPU等处理器件的电源设计堪称"心脏手术"。我曾参与过多个Xilinx UltraScale和Intel Stratix 10项目的电源验证,深刻体会到毫秒级的时序偏差就可能导致数千美元的芯片瞬间损毁。…...
鸣潮自动化工具ok-ww终极指南:3步配置解放双手的智能助手
鸣潮自动化工具ok-ww终极指南:3步配置解放双手的智能助手 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves 你是否厌倦了…...
为AI编码助手集成aislop-skill:实时代码质量检测与修复
1. 项目概述:为AI编码助手装上“质检员”如果你和我一样,日常重度依赖Cursor、Windsurf这类AI驱动的IDE,或者频繁使用Claude Code、Gemini CLI等代码生成工具,那你一定遇到过这样的场景:AI助手生成的代码,功…...
Steam成就管理终极指南:三步掌握高效成就解锁技巧
Steam成就管理终极指南:三步掌握高效成就解锁技巧 【免费下载链接】SteamAchievementManager A manager for game achievements in Steam. 项目地址: https://gitcode.com/gh_mirrors/st/SteamAchievementManager Steam Achievement Manager(SAM&…...
终极解决方案:3分钟快速修复VC++运行库缺失问题,彻底告别软件启动失败
终极解决方案:3分钟快速修复VC运行库缺失问题,彻底告别软件启动失败 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否经常遇到游戏或…...
别再只调包了!用PyTorch和DGL从零实现一个GCN层(附Cora节点分类实战代码)
从零构建图卷积网络:PyTorch与DGL实战中的底层逻辑拆解 当你第一次调用g.update_all()时,是否好奇过DGL框架背后究竟发生了什么?那些看似简单的消息传递和聚合操作,实际上隐藏着图卷积网络最精妙的设计思想。本文将带你深入GCN的数…...
AI 文档工作流里,那道正在被悄然割裂的“思想透明度”
在 AI 辅助的知识库构建、产品规格编写或 Agent 提示工程里,一份长文档从草稿到最终交付的过程,正面临一场隐形断裂。创作者先在纯文本里苦苦打磨思路,AI 却直接吐出一份排版精美、图文并茂的 HTML——看起来分享效率拉满,实际却把…...
STM32F4上给GUI换“活字”:FreeType2.13.2移植实战(含字形缓存优化)
STM32F4嵌入式GUI矢量字体革命:FreeType2.13.2深度移植与性能突围 在嵌入式系统领域,GUI界面的美观度与多语言支持能力正成为产品差异化的关键要素。传统点阵字体方案如同活字印刷时代的铅字,每个字号、每种语言都需要独立制作字库ÿ…...
12种加密音乐格式一键解密:Unlock Music开源工具完全指南
12种加密音乐格式一键解密:Unlock Music开源工具完全指南 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地址: ht…...
OpenClaw赚钱实录:从“养龙虾“到可持续变现的实践指南——OpenClaw一人公司-[一人公司的终极技术栈,从0到变现的完整光谱]
【限时99元】专栏原价299元,在专栏未完结的持续更新期间享受99元早鸟价,现在订阅同享后续专栏所有文章! 【专栏介绍】《OpenClaw赚钱实录:从“养龙虾“到可持续变现的实践指南》专栏介绍 有任何疑问均可联系博主微信(微信号:NeumannAI),作者将亲自解答并持续优化文章内…...
