关于函数指针与c++多态
原文 https://www.cnblogs.com/zhchngzng/p/4013031.html
虚函数是实现多态的重要元素,请看:

class A
{
public:
void a0(){cout <<"a0"<<endl;}
virtual void a1(){cout <<"a1"<<endl;}
}; class B: public A
{
public:
void a0(){cout <<"b.a0"<<endl;};
void a1(){cout <<"b.a1"<<endl;};
}; main()
{
A * a0 = new B ();
a0->a0();
a0->a1();
A a1 = B();
a1.a0();
a1.a1();
delete a;
}

输出是a0, b.a1, a0, a1。喻示了两点:其一,C++多态是通过指针来实现的,这和Java中通过类型转换(C#中称为装箱和拆箱)不同,因为执行A a1=B()时,首先调用了B de 构造函数构造出B对象,然后调用A的复制构造函数构造A,因此,最终调用的是A的复制构造函数,在调用函数时当然也调用A的函数了;其二,virtual的功能是使用多态时,子类的同名同参数的函数得以覆盖父类函数,而对于非虚函数,C++中在通过对象调用成员函数时,函数的入口在编译时就静态地确定了,而编译器是不在乎指针在赋值后会指向什么对象的。
这一切来自于C++的虚函数表机制。虚函数表是一个连续的内存空间,保存着一系列虚函数指针。在构造一个子对象时,内存空间最开始的4B保存一个虚函数表的入口地址。如上例中,A的虚函数表为<A::a1>,B继承A并重写了虚函数a1,因此B的虚函数表为<B::a1>,即在继承的时候,用B::a1的函数地址覆盖了A::a1的地址。于是有了下面的代码:

class A
{
public:
void a0(){cout <<"a0"<<endl;}
virtual void a1(){cout <<"a1"<<endl;}
virtual void a2(){cout <<"a2"<<endl;}
}; class B: public A
{
public:
void a0(){cout <<"b.a0"<<endl;};
void a1(){cout <<"b.a1"<<endl;};
void a2(){cout <<"b.a2"<<endl;}
}; type void ( * Function)(); main()
{
A * a = new B ();
Function p = (void (*)(void))*( (int *) *(int*)(a) + 0 ); //这里的(void (*)(void))是表明这个函数返回值为void,参数为空,等于 Function
p();
delete a;
}

其中:
a是对象的地址,
(int *) a是对象最开始4字节的地址
* (int *)a是对象的最开始的4字节
(int *) * (int *)a是对象最开始的四个字节,实际上是个地址,是什么地址呢,是虚函数表存放的地址
* (int *) * (int *)a是虚函数表的第一项,也就是第一个虚函数的地址,因此+0表示第一个函数,+1表示第二个函数,以此类推
下面分析一下这里为什么用int*不用void*,因为int* 不仅表示了指针存放的地址,也表示了指针所指的区域长度,所以可以解引用,下面参考一下别人分析指针:
链接:https://www.zhihu.com/question/46663525/answer/102300274
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
指针由两个信息构成:地址和长度(这就是为什么上面说“两个int组成的struct”)。
像这样,多于一个的信息复合在一起构成的类型,就属于复合数据类型。
指针由两个信息构成,所以指针是和int、char、float等有着本质区别的复合数据类型---切记。
题主所说的“直接用int来存放地址”,这是指针里的“地址”信息,然而指针还需要另外一个信息:长度信息-----如果只拿一个类似int的存放用于地址信息,那么长度信息就丢失了。
比如,int * p = &i;
这个p由两个信息决定,一个是p里存放的地址,另一个是int的size。
如果假设我们有一个叫pointer的类型(类似void *,但是这里就不用void *了,害怕影响初学者学习void *)用来只存放地址:pointer p = &i; 很显然,size信息就丢掉了,即我们只能通过p找到i的地址,却无法得知它所占据内存的长度。
那么为什么要把指针设定成复合数据类型呢?
举个例子来说明:比如,你要告诉计算机,去0x0001取出一个数据,然后计算机会问你:取到哪里?然后你就需要告诉计算机:取四个长度。
于是计算机就会把0x0001 0x0002 0x0003 0x0004的数据拿出来组成一个完整的数据给你。
如果指针变成了一个只保存地址的基本类型(就像上面虚构的pointer类型),那么你每次使用指针的时候,你都得显式地告诉计算机你要通过这个指针对多长的存储空间进行操作(可能你就不得不类似这样:pointer p = pointer(&i, 4);)。很显然这样很不方便且易出错。
万一你说,我就是要看看某个占4个字节的int类型的前两个字节里存放的数据,那么你可以通过位运算来轻松实现这一点。很显然位运算比起(&i, 2)要好得多。
最后举两个例子(第二个例子初学者可以先不看):
// 例子一
double d = 1.1;
double *p = &d;
cout << *p;
// 继续使用p
// 例子二
int i = 1;
void * q = &i;
cout << *q; // ERROR!!! void * is not a pointer-to-object type
cout << *static_cast<int *>(q); // correct
当编译器看到p被声明定义的时候(第二行),编译器会记住两个信息:p指向数据的类型(double)和p的初始值(d的地址,比如是0x0001)。此后,不论你在什么地方对p进行解引用操作(*p),编译器都可以立刻知道:去0x0001,以此为起点,开始取sizeof(double)长度的数据。
在第二个例子中,由于q只有一个地址信息而没有type信息,所以无法对其做解引用运算除非在解引用前先通过强制转换给它一个type信息。
因此,再强调一次,指针由两个信息共同决定,所以指针是复合数据类型。
再看一个例子:
1,通过函数指针访问子类的私有函数
2,通过函数指针访问父类的私有函数

class A
{
virtual void a0()
{
printf("A::a0 (private)\n");
}
public:
explicit A(){}
virtual void a1()
{
printf("A::a1 (public)\n");
}
}; class B : public A
{
public:
explicit B(){}
private:
int y;
virtual void a1()
{
printf("B::a1 (private)\n");
}
}; typedef void (* Function)(); main()
{
A * a = new B();
Function p;
p = ( void (*)(void) ) *( (int *) *(int*)(a) + 0 );
p();
p = (Function) *( (int*) *(int*)(a) + 1 );
p();
a->a1();
delete a;
}

其中A的虚函数表是<private A::a0, public A::a1>, B的虚函数表是<private A::a0, private B::a1>。
在第一次调用时p指向private A::a0,而第二次调用时p指向private B::a1。权限的检查是在编译阶段,因此动态的指针调用绕过了权限检查。
在第三次调用时(a->a1())时,由于对权限的检查是在编译阶段,而编译器不检查a到底指向什么对象(因为这是动态的),只看a的类型。编译器发现a是A的指针,而a1()在类A中是public函数,因此权限检查顺利地pass。随后开始执行,此时a->a1()的指针指向B::a1(),于是乎,我们成功地用父类指针A * a调用了子类B的私有函数。
关于函数指针与c++多态的更多相关文章
- 使用函数指针模拟C++多态
#include <iostream> using namespace std; class Base { public : void display() { cout << ...
- 使用函数指针和多态代替冗长的if-else或者switch-case
在编程中,if-else和switch-case是很常见的分支结构,很少在程序中不用这些控制语句.但是不能否认,在一些场景下,由于分支结构过分长,导致代码不美观且不容易维护,在<重构>一书 ...
- C 语言实现多态的原理:函数指针
C语言实现多态的原理:函数指针 何为函数指针?答案:C Programming Language. 能够查阅下,从原理上来讲,就是一个内存地址.跳过去运行相应的代码段. 既然如此,在运行时决定跳到哪个 ...
- C++ 类的多态三(多态的原理--虚函数指针--子类虚函数指针初始化)
//多态的原理--虚函数指针--子类虚函数指针初始化 #include<iostream> using namespace std; /* 多态的实现原理(有自己猜想部分) 基础知识: 类 ...
- 2014 0416 word清楚项目黑点 输入矩阵 普通继承和虚继承 函数指针实现多态 强弱类型语言
1.word 如何清除项目黑点 选中文字区域,选择开始->样式->全部清除 2.公式编辑器输入矩阵 先输入方括号,接着选择格式->中间对齐,然后点下面红色框里的东西,组后输入数据 ...
- typedef 函数指针 数组 std::function
1.整型指针 typedef int* PINT;或typedef int *PINT; 2.结构体 typedef struct { double data;}DATA, *PDATA; //D ...
- 类成员函数指针 ->*语法剖析
在cocos2d-x中,经常会出现这样的调用,如 ->*,这个是什么意思呢,如下面得这个例子: , 其实这是对类的成员函数指针的调用,在cocos2dx中,这种形式多用于回调函数的调用.如我们经 ...
- C++中怎么获取类的成员函数的函数指针?
用一个实际代码来说明. class A { public: staticvoid staticmember(){cout<<"static"<<endl;} ...
- C语言函数指针与 c#委托和事件对比
C语言: 函数指针可以节省部分代码量,写类似具有多态的函数,比如要比较最大值,如果不用函数指针就只能写比较某一类型比如int类型的max函数,这个max无法比较string的大小.函数指针的意义就不多 ...
随机推荐
- KMeans实现
KMeans实现 符号 \(K\): 聚类的个数 \(x^{(i)}\): 第i个样本 \(\mu_{1},\mu_{2},...\mu_{K}\): K个中心节点 \(c^{(i)}\): 第i个样 ...
- java.lang.NumberFormatException: For input string: "1" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang
java.lang.NumberFormatException: For input string: "1" at java.lang.NumberFormatException ...
- 从Word中拷贝字段用于MySQL建表
1.SQL 基础表 建立 USE [Test] GO /****** Object: Table [dbo].[CreateTable] Script Date: 10/17/2016 14:07:1 ...
- PD虚拟机修改RemixOS的屏幕分辨率
PD虚拟机修改RemixOS的屏幕分辨率 2017年12月02日02:13:55 by SemiconductorKING 最近要用个移动端APP,手机不方便就想在电脑跑一个,然后装了个以前用过的觉得 ...
- 行内元素的margin只能左右有效。上下无效。
行内元素的margin只能左右有效.上下无效.
- sp里拼接html table标签
DECLARE @xml NVARCHAR(MAX) --generate mail body SET @xml = CAST(( SELECT --[ID] 'td','' -- ,[Status] ...
- Table Code
post.PostToTags.Where(t => tagArray.Contains(t.PostTag.Name, comparerWihtoutCases) && !t. ...
- Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)
一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...
- Nginx 502错误总结
http请求流程:一般情况下,提交动态请求的时候,nginx会直接把 请求转交给php-fpm,而php-fpm再分配php-cgi进程来处理相关的请求,之后再依次返回,最后由nginx把结果反馈给客 ...
- 设计模式入门,单件模式,c++代码实现
// test05.cpp : Defines the entry point for the console application.// #include "stdafx.h" ...