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…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
