如何使用指向类的成员函数的指针(详解!)

2023-11-13

微笑吐舌头我们首先复习一下"指向函数的指针"如何使用?

  void print()
  {
  }
  void (*pfun)(); //声明一个指向函数的指针,函数的参数是 void,函数的返回值是 void
  pfun = print;   //赋值一个指向函数的指针
  (*pfun)();    //使用一个指向函数的指针

微笑吐舌头比较简单,不是吗?为什么*pfun需要用()扩起来呢?

微笑吐舌头因为*的运算符优先级比()低,如果不用()就成了*(pfun()).

微笑吐舌头指向类的成员函数的指针不过多了一个类的限定而已!

  class A
  {
  void speak(char *, const char *); 
  };
  
  void main()
  {
  A a;
  void (A::*pmf)(char *, const char *);//指针的声明
  pmf = &A::speak; //指针的赋值
  }

微笑吐舌头一个指向类A 成员函数的指针声明为:

微笑吐舌头void (A::*pmf)(char *, const char *);

微笑吐舌头声明的解释是:pmf是一个指向A成员函数的指针,返回无类型值,函数带有二个参数,参数的类型分别是char *和const char *。除了在星号前增加A::,与声明外部函数指针的方法一样。一种更加高明的方法是使用类型定义:例如,下面的语句定义了PMA是一个指向类A成成员函数的指针,函数返回无类型值,函数参数类型为char *和const char *:

委屈快哭了typedef void(A::*PMA)(char *,const char *);

委屈快哭了PMA pmf= &A::strcat;//pmf是 PMF类型(类A成员指针)的变量

委屈快哭了下面请看关于指向类的成员函数的使用示例:

#include <iostream>
using namespace std;

class Person
{
public:
	/*这里稍稍注意一下,我将speak()函数设置为普通的成员函数,而hello()函数设置为虚函数*/
	int value;
	void speak()	
	{
		cout << "I am a person!" << endl;
		printf ("%d\n", &Person::speak); /*在这里验证一下,输出一下地址就知道了!*/
	}
	virtual void hello()
	{
		cout << "Person say \"Hello\"" << endl;
	}
	Person()
	{
		value = 1;
	}

};



class Baizhantang: public Person
{
public:
	void speak()
	{
		cout << "I am 白展堂!" << endl;
	}
	virtual void hello()
	{
		cout << "白展堂 say \"hello!\"" << endl;
	}
	Baizhantang()
	{
		value = 2;
	}
};

typedef void (Person::*p)();//定义指向Person类无参数无返回值的成员函数的指针
typedef void (Baizhantang::*q)();//定义指向Baizhantang类的无参数无返回值的指针

int main()
{
	Person pe;
	int i = 1;
	p ip;
	ip = &Person::speak;	//ip指向Person类speak函数
	(pe.*ip)();		//这个是正确的写法!

	//--------------------------------------------
	//	result : I am a Person! 
	//			 XXXXXXXXXX(表示一段地址)
	//--------------------------------------------

	/*
	*下面是几种错误的写法,要注意!
	*		pe.*ip();
	* 		pe.(*ip)();
	*		(pe.(*ip))();
	*/

	Baizhantang bzt;
	
	q iq = (void (Baizhantang::*)())ip;	//强制转换
	(bzt.*iq)();

	//--------------------------------------------
	//	result : I am a Person!
	//			 XXXXXXXXXX(表示一段地址)
	//--------------------------------------------

	/*	有人可能会问了:ip明明被强制转换成了Baizhantang类的成员函数的指针,为什么输出结果还是:
	* I am a Person!在C++里面,类的非虚函数都是采用静态绑定,也就是说类的非虚函数在编译前就已经
	*确定了函数地址!ip之前就是指向Person::speak函数的地址,强制转换之后,只是指针类型变了,里面
	*的值并没有改变,所以调用的还是Person.speak函数,细心的家伙会发现,输出的地址都是一致的.
	*这里要强调一下:对于类的非静态成员函数,c++编译器会给每个函数的参数添加上一个该类的指针this,这也
	*就是为什么我们在非静态类成员函数里面可以使用this指针的原因,当然,这个过程你看不见!而对于静态成员
	*函数,编译器不会添加这样一个this。
	*/
	
	iq = &Baizhantang::speak;	/*iq指向了Baizhantang类的speak函数*/
	ip = (void (Person::*)())iq;	/*ip接收强制转换之后的iq指针*/
	(bzt.*ip)();

	//--------------------------------------------
	//	result : I am 白展堂!
	//--------------------------------------------

	(bzt.*iq)();//这里我强调一下,使用了动态联编,也就是说函数在运行是才确定函数地址!

	//--------------------------------------------
	//	result : I am 白展堂!
	//--------------------------------------------

	/*这一部分就没有什么好讲的了,很明白了!由于speak函数是普通的成员函数,在编译时就知道
	*到了Baizhantang::speak的地址,因此(bzt.*ip)()会输出“I am 白展堂!”,即使iq被强制转换
	*成(void (Person::*)())类型的ip,但是其值亦未改变,(bzt.*iq)()依然调用iq指向处的函数
	*即Baizhantang::speak.
	*/


	/*好了,上面讲完了普通成员函数,我们现在来玩一点好玩的,现在来聊虚函数*/
	ip = &Person::hello;	/*让ip指向Person::hello函数*/
	(pe.*ip)();

	//--------------------------------------------
	//	result : Person say "Hello"
	//--------------------------------------------

	(bzt.*ip)();

	//--------------------------------------------
	//	result : 白展堂 say "Hello"
	//--------------------------------------------

	/*咦,这就奇怪了,为何与上面的调用结果不类似?为什么两个调用结果不一致?伙伴们注意了:
	*speak函数是一个虚函数,前面说过虚函数并不是采用静态绑定的,而是采用动态绑定,所谓动态
	*绑定,就是函数地址得等到运行的时候才确定,对于有虚函数的类,编译器会给我们添加一个指针
	*vptr,指向一个虚函数表vptl,vptl里面存放着虚函数的地址,子类继承父类的时候,也会继承这样
	*一个指针,如果子类复写了虚函数,那么该表中该虚函数地址将会由父类的虚函数地址替换成子类虚
	*函数地址,编译器会把(pe.*ip)()转化成为(pe.vptr[1])(pe),加上动态绑定,结果会输出:
	*       Person say "Hello"   
	*(bzt.*ip)()会被转换成(bzt.vptr[1])(pe),自然会输出:
	*		白展堂 say "Hello"
	*ps:这里我没法讲得更详细,因为解释起来肯定是很长很长的,感兴趣的话,我推荐两本书你去看一看:
	*	第一本是侯捷老师的<深入浅出MFC>,里面关于c++的虚函数特性讲的比较清楚;
	*	第二本是侯捷老师翻译的<深度探索C++对象模型>,一听名字就知道,讲这个就更详细了;
	*当然,不感兴趣的同学这段解释可以省略,对与使用没有影响!
	*/

	iq = (void (Baizhantang::*)())ip;
	(bzt.*iq)();

	//--------------------------------------------
	//	result : 白展堂 say "Hello"
	//--------------------------------------------
	
	system("pause");
	return 0;
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用指向类的成员函数的指针(详解!) 的相关文章

  • /usr/bin/ld cannot find -lGL

    ubuntu 16 04虚拟机 装的Qt 5 10 随便写了个带UI的Demo 然后报错如下 解决如下 很多Linux发行版本 Qt安装完成后如果直接编译或者运行项目 会出现 cannot find lGL 错误 这是因为Qt找不到Open
  • 【matlab 斩波电路仿真】

    斩波电路仿真 要求 斩波电路原理 基本斩波电路 降压斩波电路搭建 结果分析 要求 斩波电路仿真 斩波电路原理 斩波电路的功能是将直流电变为另一固定电压或者可调电压的直流电 包括直接直流变流电路和简介直流变流电路 其中 直流变流电路也称为斩波
  • 某在线学习平台《数据挖掘》第八章课后习题

    此文章是本人结合课程内容和网上资料整理 难免有误差 仅供参考 1 下面哪种距离度量方法为欧几里得距离 2 以下哪个算法将两个簇的邻近度定义为不同簇的所有点对的平均逐对邻近度 它是一种凝聚层次聚类技术 AMIN 单链 BMAX 全链 C 组平
  • HK32F030MF4P6 SWD管脚功能复用GPIO

    由于电暖控制器项目上管脚不够 需要将SWD管脚复用 使用网上购买的JLINK 下载和串口调试特别方便 应用场景 往往GPIO管脚不够使用 需要将SWD下载管脚复用GPIO功能 需要用到以下设置 下载器需要接上RESET管脚 TSSOP20
  • (四)索引与数据完整性

    一 索引 1 索引的作用 快速存取数据 既可以改善数据库性能 又可以保证列值的唯一性 实现表与表之间的参照完整性 在使用ORDER BY GROUP BY子句进行数据检索时 利用索引可以减少排序和分组的时间 2 索引的分类 索引按照存储方法
  • PyQt5探索-0 用Pycharm配置PyQt5环境

    感觉是时候学习一下PyQt了 决定直接从PyQt5开始 用Pycharm做开发环境 因为之前用的Eric实在感觉不爽 今天先从配置环境开始 先安装好Pycharm Qt 安装Pycharm插件sip PyQt5 pyqt5 tools si
  • 【计算机视觉】论文单词理解—bells and whistles

    一 问题来源 最后在阅读论文的时候 遇到了一个单词 不是很理解 这个单词是bells and whistles 二 单词的理解 bells and whistles 它的含义并不是指 铃铛和口哨 其真正的含义是指 bells and whi
  • Mac 中的sublime text3 如何安装插件

    Mac 中的sublime text3 如何安装插件 相信大家在Windows系统中试用sublime text 的体验非常不错 我也是在Windows系统中使用了两年的时间 才转战Mac系统的 但是说实话 Mac系统好多东西都是十分不习惯

随机推荐

  • 时间序列学习(6)——LSTM中Layer的使用

    文章目录 1 复习一下 nn RNN 的参数 2 LSTM的 init 函数 3 LSTM forward 4 动手写一个简单的lstm层 1 复习一下 nn RNN 的参数 参数介绍 1 input size The number of
  • 梯度下降法(BGD,SGD,MSGD)python+numpy具体实现

    梯度下降是一阶迭代优化算法 为了使用梯度下降找到函数的局部最小值 一个步骤与当前位置的函数的梯度 或近似梯度 的负值成正比 如果相反 一个步骤与梯度的正数成比例 则接近该函数的局部最大值 该程序随后被称为梯度上升 梯度下降也被称为最陡峭的下
  • Proteus8.6软件安装教程

    关注公众号 免费获取资料 简介 是一款嵌入式系统仿真开发软件 同时也是英国Lab Center Electronics公司研发推出的最新的版本 该软件经实现了从原理图设计 单片机编程 系统仿真到PCB设计 真正实现了从概念到产品的完整设计
  • LeetCode338-比特位计数

    开学感觉已经是遥遥无期了 我的论文啊 我的工作啊 我该如何抉择 题目描述 给定一个非负整数 num 对于 0 i num 范围中的每个数字 i 计算其二进制数中的 1 的数目并将它们作为数组返回 示例 1 输入 2 输出 0 1 1 示例
  • deeplin显示安装空间不够,安装Deepin V20需要64GB以上磁盘空间,本文教你减小的方法...

    在安装Deepin V20操作系统时会有一个提示 说至少要64GB磁盘空间 建议在128GB以上 如果你设置少于64GB空间就不能正常安装下去 这时有方法可以解决的 你可以减小安装的磁盘空间 让32G的U盘也能够用上Deepin V20系统
  • 软件设计师上午题——第八章 UML

    软件设计师备考 UML 一 事物 1 UML 2 UML事物 1 结构事务 静态事务 2 行为事务 动态事务 3 分组事务 组织部分 4 注释事务 解释部分 二 关系 1 依赖关系 2 关联 聚合 组合关系 3 泛化关系 4 实现关系 5
  • 基于教学优化算法求解TSP问题(附Matlab代码)

    基于教学优化算法求解TSP问题 附Matlab代码 旅行商问题 Traveling Salesman Problem 简称TSP 是一个经典的组合优化问题 其目标是找到一条最短路径 使得一个旅行商能够访问一系列城市并返回起始城市 同时每个城
  • wordpress发表文章之后查看显示404

    刚刚写了一篇LAMP wordpress搭建博客的 发表成功之后想看一下效果 结果显示404 OMG 难道没成功 在后台所有文章里显示有 但是点击却显示404 404表示为指向的链接不存在 于是发现自己访问的地址为http www zhao
  • Python入门(一)——环境的搭建,创建第一个Python项目:Hello World

    Python入门 一 环境的搭建 创建第一个Python项目 Hello World 一 安装环境 安装就很简单了 我们进入官网Python 然后下载对应的版本就好了 如果你是Liunx的话 可以直接运行python 关于环境的配置 大家百
  • Uncaught SyntaxError: The requested module ‘/node_modules/.vite/...‘ does not provide an export ...

    项目启动正常 但是页面不显示且打印报错 Uncaught SyntaxError The requested module node modules vite deps vue js v 84e66e8d does not provide
  • 缺失值填充2——python 热卡填充(Hot deck imputation)、冷卡填充(Cold deck imputation)

    基本概念 热卡填充 在完整数据中找到一个与它最相似的对象 用最相似的值填充当前值 冷卡填充 通过其他途径找到能填充缺失部分的值 热卡填充其实就是使用KNN去预测的一种特殊形式 KNN是参考K个 而热卡填充是参考最近的1个 所以热卡填充可以用
  • 95-36-025-ChannelHandler-ChannelHandlerAdapter

    文章目录 1 概述 2 继承体系 3 isSharable 1 概述 2 继承体系 3 isSharable 作为ChannelHandler的默认实现 ChannelHandlerAdapter有个重要的方法isSharable 代码如下
  • Windows XP的网上邻居访问设置

    Windows XP的网上邻居访问设置 more Windows XP的网上邻居有许多差强人意的地方 访问速度慢 无法访问其他主机 其实这些问题通过简单的设置都可以很好地解决 在这之前先确保能够互相PING通 1 开启guest账户 2 允
  • JVM---堆(概述)

    堆 概述 一个JVM实例只存在一个堆内存 堆也是Java内存管理的核心区域 Java堆区在JVM启动的时候即被创建 其空间大小也就确定了 是JVM管理的最大一块内存空间 堆内存的大小是可以调节的 Java虚拟机规范 规定 堆可以处于物理 上
  • 数据结构-二叉树-更新完整版

    目录 二叉树初识 实现二叉树的功能 功能前操作 遍历功能 操作 计算 查询功能 源码 因为上次二叉树的文章的功能不全 所以这次更新一个完整版的二叉树文章 这篇文章有些功能是需要用到队列知识的 所以对队列不了解的可以先去看看这篇队列 详解 因
  • Vue视频插件(vue-video-player)

    前言 在新的项目中用到了大量的视频 由此也学习了一款vue的前端视频播放插件 接下来分享一下 vue video player安装 npm install vue video player save npm install save vid
  • scala简介与安装

    一 简介 1 scala是啥 Scala用一种简洁的高级语言将面向对象和函数式编程结合在一起 Scala的静态类型有助于避免复杂应用程序中的错误 其JVM和JavaScript运行时使您能够轻松访问庞大的库生态系统来构建高性能系统 2 为啥
  • C++:派生类的默认构造函数和拷贝构造函数调用基类构造函数的机制(含程序验证)

    1 如果基类定义了不带参数的默认构造函数 则编译器为派生类自动生成的默认构造函数会调用基类的默认构造函数 2 如果基类定义了拷贝构造函数 则编译器为派生类自动生成的拷贝构造函数同样会调用基类的拷贝构造函数 3 如果基类定义了带参数的构造函数
  • Git-3-项目初体验

    Git的使用需求 IAR工程文件夹 包含多个工程 多个文件夹和文件 c文件 h文件以及库文件 编译文件等等 a 只跟踪c文件和h文件的版本 实现一个大的多层文件夹中的 c和 h文件 能够更新 b 建立分支 分别对各个工程进行操作 proje
  • 如何使用指向类的成员函数的指针(详解!)

    我们首先复习一下 指向函数的指针 如何使用 void print void pfun 声明一个指向函数的指针 函数的参数是 void 函数的返回值是 void pfun print 赋值一个指向函数的指针 pfun 使用一个指向函数的指针