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…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
