继承类怎么改变鼠标指针图案不改变

C++为什么基类指针不能指向继承方式为protected与private的派生类对象?_百度知道后使用快捷导航没有帐号?
查看: 3399|回复: 0
看过关于C++的书籍,知道了如果类中有虚函数,那么对象的前四个字节就是一个指向称为虚函数表的指针。
& && &使用了多继承之后的情况是怎样呢?我以前并没有认真考虑过这个问题,只是想当然地认为多个基类的虚函数表会合成一张表。
& && &但是后来看过一些关于C++内存对象模型的文章之后我知道我错了。这些文章我大体看懂了,但是有些细节不很明白,于是决定自己写代码来进行实验。
& && &我用VC7写了一个动态库,在某些程度上模仿了COM:
#define IID_X 1
#define IID_Y 2
class IBase
& & virtual int __stdcall Query(int iid,void ** ppv)=0;
& & virtual int __stdcall AddRef()=0;
& & virtual int __stdcall Release()=0;
class IX:public IBase
& & virtual unsigned long __stdcall FuncX1()=0;
class IY:public IBase
& & virtual unsigned long __stdcall FuncY1()=0;
class XY:public IX,IY
protected:
& & int m_
& & virtual ~XY();
& & virtual int __stdcall Query(int iid,void ** ppv);
& & virtual int __stdcall AddRef();
& & virtual int __stdcall Release();
& & virtual unsigned long __stdcall FuncX1();
& & virtual unsigned long __stdcall FuncY1();
extern &C&
int _declspec(dllexport) NewObject(IX ** ppv);
#include &stdafx.h&
#include &interf.h&
int XY::Query(int iid,void ** ppv)
& & if(IID_X==iid)
& && &&&*ppv=(IX *)
& && &&&((IX*)(*ppv))-&AddRef();
& & else if(IID_Y==iid)
& && &&&*ppv=(IY *)
& && &&&((IY*)(*ppv))-&AddRef();
& && &&&*ppv=NULL;
& && &&&return 0;
& & return 1;
int XY::AddRef()
& & return ++m_
int XY::Release()
& & if(0==--m_ref)
& & return m_
& & m_ref=1;
XY::~XY(){}
unsigned long XY::FuncX1()
& & return (unsigned long)
unsigned long XY::FuncY1()
& & return (unsigned long)
MultiExt.cpp:
// MultiExt.cpp : 定义 DLL 应用程序的入口点。
#include &stdafx.h&
#include &interf.h&
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
& & return TRUE;
int _declspec(dllexport) NewObject(IX ** ppv)
& & *ppv=(IX *)new XY;
& & if(NULL==*ppv)
& && &&&return 0;
& & return 1;
& && &这是一个动态库。编译好之后,又用PureBasic写了一段程序来调用这个动态库:
ProtoType ProtoNewObject(ppv)
Interface IBase
& & Query(iid,ppv)
& & AddRef()
& & Release()
EndInterface
Interface IX Extends IBase
& & FuncX1()
EndInterface
Interface IY Extends IBase
& & FuncY1()
EndInterface
OpenLibrary(0,&MultiExt.dll&)
NewObject.ProtoNewObject=GetFunction(0,&NewObject&)
NewObject(@x)
x\Query(2,@y)
y\Release()
x\Release()
CloseLibrary(0)
& && &运行这个程序,就会发现一些让人惊讶的内容,2个Debug输出的结果分别为:36884。原来x指针和y指针指向了不同的地方。更明确地说,是y比x靠后4字节。x是由动态库函数NewObject获取的,y是由x调用Query得来的。Query做了什么呢?看看上面的 xxyy.cpp,原来Query将对象的指针强制转换成为(IY*)并通过参数输出。我原以为对指针进行类型转换只是对指针算术产生一些影响,没想到此时的类型转换竟然改变了指针的值。那么为什么和x相比y偏移了4个字节呢?
& && &看了一些文章知道了原来在多继承的时候,对象中已经不止一个虚函数表指针了。像上面的示例程序,XY的对象中包含两个虚函数表指针,一个来自IX,一个来自IY。由于IX和IY均没有数据成员,所以属于IY的虚函数表指针就放在了属于IX的虚函数表指针的后面,属于IX的虚函数表指针放在了对象的最前面。基于这种原理,x和y的值相差4。
& && &但是x与y的不同带来了一个问题,那就是y没有指向对象的首地址,可是通过一个接口指针调用虚成员函数时,首先通过查找虚函数表来确定函数指针,这一点没有问题,因为y就指向 指向IY虚函数表的指针(有点拗口)。问题是调用函数时系统会把接口指针作为this参数传递给函数,然而y已经不能代表对象的首地址,也就是说传递给函数的this参数不是对象的首地址。我想了想,应该是编译器在函数里“偷偷摸摸”加了一句:“this-=4;”,大家会说this是const,的确,不过那仅仅针对程序员,编译器可以为所欲为。
& && &当然,这只是一个想法,要证明这个想法得进行实验,得到能够说明问题的数据,我在上面的PureBasic程序中加了一些语句,修改后的程序如下(如果对下文的内容不很明白可以参看《再谈PureBasic的Interface》):
ProtoType ProtoNewObject(ppv)
Interface IBase
& & Query(iid,ppv)
& & AddRef()
& & Release()
EndInterface
Interface IX Extends IBase
& & FuncX1()
EndInterface
Interface IY Extends IBase
& & FuncY1()
EndInterface
ProtoType ProtoQuery(this,iid,ppv)
ProtoType ProtoAddRef(this)
ProtoType ProtoRelease(this)
ProtoType ProtoFuncY1(this)
Structure IYVTable
& & Query.ProtoQuery
& & AddRef.ProtoAddRef
& & Release.ProtoRelease
& & FuncY1.ProtoFuncY1
EndStructure
OpenLibrary(0,&MultiExt.dll&)
NewObject.ProtoNewObject=GetFunction(0,&NewObject&)
NewObject(@x)
x\Query(2,@y)
*object.LONG=y
*iyvt.IYVTable=*object\l
Debug *iyvt\FuncY1(11)
y\Release()
x\Release()
CloseLibrary(0)
& && &我们给FuncY1传入11,Debug显示结果7。这足够证明在XY::FuncY1()中编译器加入了“this-=4;”。当然不一定总是减4,减几得根据虚函数表指针出现在对象中的位置来定,例如,我们给class IX添加一个数据成员long x,再用上面的PureBasic程序实验,传入11,得到Debug输出“3”,原来在属于IX的虚函数表指针和属于IY的虚函数表之间夹了一个 long x,所以只有将this自减8才能指向对象的首地址。
& && &C++的编译程序将这些复杂的东西都隐藏了起来,所以我们写起程序来轻松了很多。
Powered by Discuz!
& Comsenz Inc.博客访问: 11660
博文数量: 4
博客积分: 0
博客等级: 民兵
技术积分: 68
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
#include <iostream>
using namespace std;
class Door
&&&&virtual void Open() { cout<<"I'm a thinly pathetic shelter, just kick me\n"<<endl; } //运行时的多态性,如果不加virtual就是编译时的多态
//&&&&void Open() { cout<<"I'm a thinly pathetic shelter, just kick me\n"<<endl; }//编译时的多态性
class SaftyDoor:public Door
&&&&void Open()
&&&&&&&&cout<<"Look at the key hole, do you have the key?\n"<<endl;
class SteelDoor:public SaftyDoor
&&&&void Open()
&&&&&&&&cout<<"Kick? you little pathetic comic\n"<<endl;
//还可以调用祖宗的Open()
class FakeDoor:public SaftyDoor
&&&&void Open()
&&&&&&&&cout<<"I am new and splendid, BUT still ";
&&&&&&&&Door::Open();
&&&&//假冒伪劣安全门糊弄人的新功能
&&&&void Alert()
&&&&&&&&cout<<"bee bee bee bee....\n"<<endl;
void main()
&&&&while(1)
&&&&&&&&Door* door_BoughtWhenIAmPoor = new Door();
&&&&&&&&Door* door_BoughtWhenIEarnedMoney = new SaftyDoor();
&&&&&&&&Door* door_BoughtWhenIAmRich = new SteelDoor();
&&&&&&&&Door* door_BoughtWhenIWatchedDoorAds = new FakeDoor();
&&&&&&&&door_BoughtWhenIAmPoor->Open();
&&&&&&&&door_BoughtWhenIEarnedMoney->Open();
&&&&&&&&door_BoughtWhenIAmRich->Open();
&&&&&&&&door_BoughtWhenIWatchedDoorAds->Open();
&&&&&&&&delete door_BoughtWhenIAmPoor;
&&&&&&&&delete door_BoughtWhenIEarnedMoney;
&&&&&&&&delete door_BoughtWhenIAmRich;
&&&&&&&&delete door_BoughtWhenIWatchedDoorAds;
&&&&&&&&cin.get();
--------------------------------------------------------------------------------------------------------------------
最近在工作中用了几套三方SDK写程序, 发现一个特点, 这几套SDK在使用上都一个风格, 形如本例
本例是由百度百科里的例子改来的, 原例都是A,B,C莫名其妙的变量名, 函数名, 难看!
改成这样就好多了, 有助理解.
--------------------------------------------------------------------------------------------------------------------
1.& & 这种风格似乎使程序更好扩展, 子类可以重写属于自己的方法, 也可以调用父类或祖宗类的方法, 而却用祖宗类的类型定义
2.& & 由于用的是指针, 所以可以实现动态分配类占的空间, 随时释放
阅读(1596) | 评论(0) | 转发(1) |
下一篇:没有了
相关热门文章
给主人留下些什么吧!~~
请登录后评论。原文地址:/fangyukuan/archive//1747516.html
在C/c&#43;&#43;中,类型之间的转换是经常被遇到的,在C&#43;&#43;中,经常会遇到指针的类型转换,比如将派生类指针转换为基类指针,将基类指针转换为派生类指针。指针的本质其实就是一个数字,用以记录进程虚拟内存空间中的地址编号,而指针的类型决定了编译器对其指向的内存空间的解释方式。
基于上面的理解,我们&#20284;乎可以得出一个结论,C&#43;&#43;中对指针进行类型转换,不会改变指针的&#20540;,只会改变指针的类型(即改变编译器对该指针指向内存的解释方式),但是这个结论在C&#43;&#43;多重继承下是 不成立的。
以下程序例子:
#include &iostream&
char m_a[32];
char m_b[64];
class cd : public ca,public cb{
char m_d[128];
int main(int argc,char **argv)
ca *pA = (ca *)pD;
cb *pB = (cb *)pD;
cout && pA &&
cout && pB &&
cout && pD &&
cout && (pD == pB) &&
这段代码的输出结果是:
可以看出,指向同一个堆上new出来的内存指针,在经过类型转换之后,其&#20540;会发生改变。究其原因,要从C&#43;&#43;中多重继承的内存布局说起。
同时我们注意到,pB与pD的指针差&#20540;正好是ca占用的内存大小32字节,而pA与pD都指向了同一段地址。这是因为,将一个派生类的指针转换成某一个基类指针,编译器会将指针的&#20540;偏移到该基类在对象内存中的起始位置。
此时,pA 与 pD都指向ca对象的起始地址,pB 指向其起始地址刚好与ca对象的起始地址差32,所以可以看到pB打印出的地址比pA和pD的地址差32,而pA与pD均指向首地址。
接下来输出1表示pB和pD地址是相同的,其实这也是编译器屏蔽了这种指针的差异,当编译器发现一个指向派生类的指针和指向其某个基类的指针进行==运算时,会自动将指针做隐式类型提升已屏蔽多重继承带来的指针差异。因为两个指针做比较,目的通常是判断两个指针是否指向了同一个内存对象实例,在上面的场景中,pD和pB虽然指针&#20540;不等,但是他们确确实实都指向了同一个内存对象(即产生的内存对象 ),所以编译器又在此处插了一脚,让我们可以安享==运算的上层语义。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:24125次
排名:千里之外
原创:52篇
转载:14篇
(2)(2)(2)(1)(2)(2)(7)(1)(4)(3)(15)(2)(22)(1)

我要回帖

更多关于 golang 指针继承 的文章

 

随机推荐