??xml version="1.0" encoding="utf-8" standalone="yes"?>
模拟它的MVO架构Q不q没有原代码Q所以很难a
设计了一个交互绘囑֟本类Q?br />但是q有错误Q鼠标左键点M?br /> winGDI.cpp中出错?br />请大虾指教一二?br />我已l在q个问题上花了很多的心思?br />其中最重要的就是Painter抽象cȝ设计
他的子类QBufferDCl承CDC
而SGView包含Painter指针Q方便在SGView ::drawEntity调用?br />
SGObject - 对象的抽象类Q也是几何对象
SGModel- 负责对象理Q没有实现所有的功能Q准备用SceneTree来实?br />SGView - 负责昄SGModel中的数据Q关键的函数
void SGView::drawEntity(SGObject* pObj)
{
pObj->draw(m_pPainter,this)
}
Painter - 装CDC的功?见代?br />
在CSGView创徏的时候创建Painter对象
很可能这里有问题Q!Q!Q?br />void CSGView::OnCreate(..)
{
CDC* pDC = GetDC();
Painter* painter = new QBufferDC(pDC);
m_pSGView->setPainter(painter);
}
MFC 相关的Document/View架构
CSGDocument - 理SGModel
CSGView - 和SGView建立联系Qƈ负责把windows的消息发送给SGView
见原代码
SGActionManager - 负责工具的管?br />SGBaseAction - 工具的抽象基c?br />SGActionDrawLine - l制直线的工?br />
源代码连接:
http://www.shnenglu.com/Files/richardzeng/MVOTest.rar
ȝQ画L资源l承resource
Z避免发生资源泄露和resource的管?br />设计ResourceManagerc,负责资源的创建,加蝲和卸载以及删?/p>
两个抽象c?Resource ?ResourceManager
两个具体c?ConcreateResource ?ConcreateResourceManager
分别z于上面的抽象c?/p>
以上设计是看?OGRE 游戏引擎的资源管理部分,
对它的资源管理类ResourceManager不是很理?/p>
resource z了pen,brush{类
pencd以来自文Ӟ也可以自己创建SubPen d到SubPenList?/p>
ResourceManager 负责创徏资源Resource
1. 如果我在抽象?ResourceManager 声明 createRes函数Qƈq回基类resource
势必会要强制转换Q然后在用到具体的Resource时候又要{换回?/p>
2. 如果我在具体c?ConcreateResourceManager 声明 createConcreateRes函数
那么q费了我应用设计模式设计这么多c?/p>
// abstract class for resource
class Resource{
public:
// standard constructor
Resource(const string& name, const string& group)
:mName(name),mGroup(group){}
~Resource(){}
protected:
// prevent default construct
Resource():mName(""),mGroup(""){}
string mName;
string mGroup;
static unsigned long mHandle;
};
// subclass of resource
// concreateResource such as PEN
class Pen:
public Resource{
Pen(const string& name, const string& group)
:Resource(name,group){}
~Pen(){}
void loadfromFile(string& filename);
// add into vector
void addSubPen(SubPen* sub){
mSubPenList.push_back(sub);
}
public:
typedef std::vector<SubPen> SubPenList;
SubPenList mSubPenList;
};
class
// abstract class for resource manager
class ResourceManager{
public:
ResourceManager(){}
~ResourceManager(){}
public:
// here , I cannot understand OGRE degsin
Resource* createRes(const string& name,const string& group);
// resource map
typedef std::map<string,Resource*> ResourceMap;
ResourceMap mResources;
};
// subclass ResourceManager
class ConcreateResourceManager
:public ResourceManager
{
ConcreateResourceManager(){}
~ConcreateResourceManager(){}
// how can design here!!
Pen* createPen(const string& name,const string& group){}
}
#include <iostream>
using namespace std;
//单g模板c?/div>
template<typename T> class Singleton
{
protected:
static T* m_Instance;
Singleton(){}
virtual~Singleton(){}
public:
//实例的获?/div>
static T* Instance()
{
if(m_Instance==0)
m_Instance=new T;
return m_Instance;
}
//单gcȝ释放
virtual void Release()
{
if(m_Instance!=0)
{
delete m_Instance;
m_Instance=0;
}
}
};
//单g模板试c?/div>
class Test:public Singleton<Test>
{
friend class Singleton<Test>; //声明为友员,不然会出?/div>
protected:
Test()
{
a=b=c=0;
}
virtual ~Test(){}
public :
int a;
int b;
int c;
};
//初始化静态成员。。?/div>
template<> Test*Singleton<Test>::m_Instance=0;
//以下为测试代?/div>
void main()
{
Test*t=Test::Instance();
t->a=5;
t->b=25;
t->c=35;
cout<<"t: a="<<t->a<<" b="<<t->b<<" c="<<t->c<<endl;
Test*t2;
t2=Test::Instance();
cout<<"t2 a="<<t2->a<<" b="<<t2->b<<" c="<<t2->c<<endl;
t2->Release();
}
|
char toUpper(const char& ch) { return ch & 0x5F; } char toLower(const char& ch) { return ch | 0x20; } |
字段?/SPAN> |
字节?/SPAN> |
cd |
描述 |
Total_Length |
4 |
Unsigned Integer |
消息总长?/SPAN>(含消息头及消息体) |
Command_Id |
4 |
Unsigned Integer |
命o或响应类?/SPAN> |
Sequence_Id |
4 |
Unsigned Integer |
消息水?/SPAN>,序累加,步长?/SPAN>1,循环使用Q一对请求和应答消息的流水号必须相同Q?/SPAN> |
字段?/SPAN> |
字节?/SPAN> |
属?/SPAN> |
描述 |
Source_Addr |
6 |
Octet String |
源地址Q此处ؓSP_IdQ即SP的企业代码?/SPAN> |
AuthenticatorSource |
16 |
Octet String |
用于鉴别源地址。其值通过单向MD5 hash计算得出Q表C如下: AuthenticatorSource = MD5Q?/SPAN>Source_Addr+9 字节?/SPAN>0 +shared secret+timestampQ?/SPAN> Shared secret ׃国移动与源地址实体事先商定Q?/SPAN>timestamp格式为:MMDDHHMMSSQ即月日时分U,10位?/SPAN> |
Version |
1 |
Unsigned Integer |
双方协商的版本号(高位4bit表示ȝ本号,低位4bit表示ơ版本号)Q对?/SPAN>3.0的版本,?/SPAN>4bit?/SPAN>3Q低4位ؓ0 |
Timestamp |
4 |
Unsigned Integer |
旉戳的明文,由客L产生,格式?/SPAN>MMDDHHMMSSQ即月日时分U,10位数字的整型Q右寚w ?/SPAN> |
FeedBack:
int A(char *p); // 回调函数
typedef int(*CallBack)(char *p) ; // 声明CallBack cd的函数指?
CallBack myCallBack ; // 声明函数指针变量
myCallBack = A; // 得到了函数A的地址
B(CallBack lpCall,char *pProvide)
{
........... // B 的自己实现功能语?BR> lpCall(PpProvide); // 借助回调完成的功?Q也是A函数来处理的?
........... // B 的自己实现功能语?BR>}
// -------------- 使用例子 -------------
char *p = "hello!";
CallBack myCallBack ;
myCallBack = A ;
B(A, p);
class CUploadFile : public CDialog
{
......
int Send(LPCTSTR lpServerIP, LPCTSTR lpServerPort, LPCTSTR UploadFilePath) ;
static int GetCurState(int nCurDone, int nInAll, void * pParam) ;
......
}
int CUploadFile ::Send(LPCTSTR lpServerIP, LPCTSTR lpServerPort, LPCTSTR UploadFilePath)
{
... // 导出传输数据的函?
int ret = Upload( (LPSTR)(LPCTSTR)m_strData,
GetCurState, // 在这个回调函C处理界面
this, // CUploadFile 的自w指?Q也是pParam 所接受的参?
(LPSTR)(LPCTSTR)UploadFilePath,
"",
"",
);
}
int CUploadFile ::GetCurState(int nCurData, int nInAll, void * pParam)
{
.........
UploadFile *pThis = (UploadFile *)pParam; // nCurData 当前以传出的数据?
// nInAll ȝ数据?BR> // 有了pThis可以对界面进行各U操作了?
.............
}
class CUploadData
{
......
typedef int(*SetUploadCaller)(int nCurData, int nInAll, void * pParam);
int UploadFile(LPCTSTR lpFileNamePath,LPVOID lparam,SetUploadCaller Caller );
// 接受外界出入的参?主要是回调函数的地址通过参数Caller,
int Send(LPCTSTR lpServerIP, LPCTSTR lpServerPort, LPCTSTR UploadFilePath) ;
...... // 注意此时不在需要GetCurState 函数??BR>}
class CShowGUI: public CDialog
{
.......
typedef int(*SetUploadCaller)(int nCurData, int nInAll, void * pParam);
void SetCallBack(LPCTSTR strPath);
static int GetCurState(int nCurData, int nInAll, void * pParam) ;
CUploadData m_Uploa
d ; // 数据上传cL界面昄cȝ一个成员变量?
.......
}
void CShowGUI :: SetCallBack(LPCTSTR strPath)
{
CUploadData myUploadData ;
SetUploadCaller myCaller; // 声明一个函数指针变?
myCaller = CurState ; // 取得界面处理函数的地址
myUploadData .UploadFile(strPath,this,myCaller); // 界面处理cȝ函数传入,实现了数据传入与界面处理的分?.
}
fp 是一个典型的函数指针Q用于指向无参数Q无q回值的函数?/P>
fp2 也是一个函数指针,用于指向有一个整型参敎ͼ无返回值的函数?BR> 当然Q有l验人士一般都会徏议用typedef来定义函数指针的cdQ如Q?/P>
函数指针之所以让初学者畏惧,最主要的原因是它的括号太多了;某些用途的函数指针Q往往会让人陷在括号堆中出不来Q这里就不D例了Q因Z是本文讨论的范围Qtypedef Ҏ可以有效的减括L数量Q以及理清层ơ,所以受到推荐。本文暂时只考虑单的函数指针Q因此暂不用到typedef?BR>
假如有如下两个函敎ͼ
现在需要通过函数指针来调用,我们需要给指针指定函数Q?/P>
对于此两U调用方法,效果完全一P我推荐用前一U。后一U不仅仅是多打了键盘Q而且也损׃一些灵zL。这里暂且不说它?BR>
C++cd安全。也是_不同cd的变量是不能直接赋值的Q否则轻则警告,重则报错。这是一个很有用的特性,常常能帮我们扑ֈ问题。因此,有识之士认ؓQC++中的M一外警告都不能忽视。甚x人提出,~译的时候不能出CQ何警告信息,也就是说Q警告应该当作错误一样处理?BR>
比如Q我们把f1赋值给fp2Q那么C++~译?vc7.1)׃报错Q?/P>
q样Q编译器可以帮我们找出编码上的错误,节省了我们的排错旉?BR>
考虑一下C++标准模板库的sort函数Q?/P>
比如Q我们有一个整型数l:
要对它进行升序排序,我们需定义一个比较函敎ͼ
然后用:
要是惛_它进行降序排序,我们只要换一个比较函数就可以了。C/C++的标准模板已l提供了less和great函数Q因此我们可以直接用下面的语句来比较Q?nbsp;
q样Q不需要改变sort函数的定义,可以按LҎq行排序Q是不是很灵z?
q种用法以C++的标准模板库(STL)中非常流行。另外,操作pȝ中也l常使用回调(CallBack)函数Q实际上Q所谓回调函敎ͼ本质是函数指针?/P>
看v来很单吧Q这是最普通的C语言指针的用法。本来这是一个很妙的事情,但是当C++来Ӟ世界开始变了样?BR> 假如Q用来进行sort的比较函数是某个cȝ成员Q那又如何呢Q?BR>
数据指针 | T * |
成员数据指针 | T::* |
函数指针 | R (*)(...) |
成员函数指针 | R (T::*)(...) |
q样一来,C++的指针就变得很尴:我们需要一U指针能够指向同一cd的数据,不管q个数据是普通数据,q是成员数据Q我们更需要一U指针能够指向同一cd的函敎ͼ不管q个函数是静态函敎ͼq是成员函数。但是没有,臛_从现在的C++标准中,q没有看到?BR>
沐枫|志 C++指针探讨(?成员函数指针
自从有了c,我们开始按照 数据Q操作 的方式来l织数据l构Q自从有了模板,我们又开始把 数据 ?法 分离Q以侉K用,实在够折腾h的。但不管怎么折腾Q现在大多数函数都不再单w,都嫁l了c,q了围城。可是我们仍焉要能够自p用这些成员函数?BR> 考虑一下windows下的定时调用。SetTimer函数的原型是q样的:
但是昄QC++E序员肯定不会因此而满뀂这里头有许多被C++批判的不安定因素。它使用了C++中被认ؓ不安全的cd转换Q不安全的void*指针Q等{等{。但q是pȝ为C语言留下的调用接口,q也p了。那么假如,我们在C++E序中如何来调用成员函数指针呢?
如下例,我们打算对vector中的所有类调用其指定的成员函数Q?/P>
Ҏ1Q编译不能通过。for_each只允许具有一个参数的函数指针或函数对象,哪怕A::doit默认有一个this指针参数也不行。不是for_each没考虑到这一点,而是Ҏ做不刎ͼ
Ҏ2Q显然是受到了beginthread的启发,使用一个静态函数来转调用,哈哈成功了。但是不爽!q不是C++?BR> Ҏ3Q呼Q好不容易啊Q终于用mem_fun_ref包装成功了成员函数指针?BR> gҎ3不错Q又是类型安全的Q又可以通用Q-慢着Q首先,它很丑,哪有调用普通C函数指针那么漂亮啊(见方?Q,用了一大串包装Q又是尖括号又是圆括Pq少不了&P其次Q它只能包装不超q一个参数的函数Q尽它在for_each中够用了Q但是你要是想用在超q一个参数的场合Q那只有一句话Q不可能的Q务?BR>
是的Q在标准C++中,q是不可能的d。但事情q不L悲观的,臛_有许多第三方库提供了越mem_fun的包装。如boost::function{等。但是它也有限制Q它所支持的参C然是有限的,只有十多个,管够你用的了;同样Q它也是丑陋的,永远不要惛_能够单的?amp;来搞定?BR>
也许Q以失去丽的代P来换取质量上的保证,q也是C++对于函数指针的一U无奈吧…?BR>
期待C++0x版本。它通过可变模板参数Q能够让mem_fun的参数达到无限个…?BR>
Q-Q-Q-Q-
BTW: C++Builder扩展了一个关键字 closure Q允许成员函数指针如同普通函数指针一样用。也许C++0x能考虑一下…?/P>
按照面向q程的基本原则,划分pȝ功能模块、模块细分到函数、生成系l整体的l构模型Q似乎在整个q程中没有Q何东西可以用来提供系l扩展,其实解决的方法还是有的,q根救命E草是回调机制?/P>
一谈到回调机制Q当然就不了我们的主角Q系lAPI(通常都是)和回调函敎ͼq两者缺一不可。其实回调的基本思想是ql给我们提供一些接口,也就是常使用的APIQ这U函数可以将某个其他函数的地址作ؓ其参C一Q而且可以利用该地址对这个函数进行调用,而被调用的函数就是我们通常所说的回调函数了?BR>下面l个回调函数使用的小例子Q?BR>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
//相当于我们提到的pȝAPI
mainFunc( void* userFunc )//当然参数不会q么单,只是模拟
{
while (...)
{
printf("ok!");
//调用回调函数?BR> if (userFunc!=NULL)
userFunc();
}
}
可以看出MainFunc可以Ҏ函数userFunc的地址调用它?BR>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
q样使用者只需要定义一个函敎ͼvoid myFunc()Q然后按照mainFunc(&myFunc)(&只表CZ递的是函数的地址Q无具体含义)Q就可以让我们的mainFunc来调用myFunc从而实现相应的功能Q这样当然可以完成我们预期的目的Q扩展现有系l?/P>
在windowspȝ中,支持q种回调机制的系lAPI不占数Q像实现ListControl排序的SortItem()函数Q还有操作Font使用的函数EnumFontFamilies()都有提供q种回调机制Q得我们的用户有机会添加自己期望的功能实现。当Ӟ使用回调函数q不是一个轻杄事情Q如果我们的pȝ中存在了大量的回调函数是很难理的,q个׃pȝ中存在大量全局变量一P出现多个函数争相讉K同一个变量我们就很难使用单的逻辑来处理,Ҏ陷入混ؕQ因此,管回调机制可以在某U程度上辑ֈ我们的目的,但切不可乱加使用Q不然后果很N料?/P>
当然至于详细的回调函数实玎ͼq需要大家潜心研IӞq里我只是ȝ一下:
1 回调函数是由开发者按照一定的原型q行定义的函?每个回调函数都必遵循这个原型来设计)
例如Q?BR>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
BOOL CALLBACK DialogProc(
HWND hwndDlg, // handle of dialog box
UINT uMsg, // message
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
说明Q?BR>回调函数必须有关键词 CALLBACK
回调函数本n必须是全局函数或者静态函敎ͼ不可定义为某个特定的cȝ成员函数
2 回调函数q不由开发者直接调用执?只是使用pȝ接口API函数作ؓL)
3 回调函数通常作ؓ参数传递给pȝAPIQ由该API来调?BR>4 回调函数可能被系lAPI调用一ơ,也可能被循环调用多次(SortItem是自调?
最后说句题外话Q其实windowspȝ中还有另一U机Ӟ消息机制Q也是一个比较不错的工具Q能够ؓ很多实际的问题提供解x法,q个以后再ȝ了?/P>
需要一个数据对象ؓ整个c而非某个对象服务,同时又力求不破坏cȝ装?卌求此成员隐藏在类的内部,对外不可见?/P>
static的内部机Ӟ
静态数据成员要在程序一开始运行时必d在。因为函数在E序q行中被调用Q所以静态数据成员不能在M函数内分配空间和初始化?BR> q样Q它的空间分配有三个可能的地方,一是作为类的外部接口的头文Ӟ那里有类声明Q二是类定义的内部实玎ͼ那里有类的成员函数定义;三是应用E序的mainQ)函数前的全局数据声明和定义处?BR> 静态数据成员要实际地分配空_故不能在cȝ声明中定义(只能声明数据成员Q。类声明只声明一个类的“尺寸和规格”,q不q行实际的内存分配,所以在cd明中写成定义是错误的。它也不能在头文件中cd明的外部定义Q因为那会造成在多个用该cȝ源文件中Q对光复定义?BR> static被引入以告知~译器,变量存储在E序的静态存储区而非栈上I间Q静?BR>数据成员按定义出现的先后序依次初始化,注意静态成员嵌套时Q要保证所嵌套的成员已l初始化了。消除时的顺序是初始化的反顺序?/P>
static的优势:
可以节省内存Q因为它是所有对象所公有的,因此Q对多个对象来说Q静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一P但它的值是可以更新的。只要对静态数据成员的值更Cơ,保证所有对象存取更新后的相同的|q样可以提高旉效率?/P>
引用静态数据成员时Q采用如下格式:
<cd>::<静态成员名>
如果静态数据成员的讉K权限允许的话(即public的成?Q可在程序中Q按上述格式
来引用静态数据成员?/P>
PS:
(1)cȝ静态成员函数是属于整个c而非cȝ对象Q所以它没有this指针Q这导?BR>了它仅能讉Kcȝ静态数据和静态成员函数?BR> (2)不能静态成员函数定义ؓ虚函数?BR> (3)׃静态成员声明于cMQ操作于其外Q所以对其取地址操作Q就多少有些Ҏ
Q变量地址是指向其数据cd的指?Q函数地址cd是一个“nonmember函数指针”?/P>
(4)׃静态成员函数没有this指针Q所以就差不多等同于nonmember函数Q结果就
产生了一个意想不到的好处Q成Z个callback函数Q得我们得以将C++和C-based X W
indowpȝl合Q同时也成功的应用于U程函数w上?BR> (5)staticq没有增加程序的时空开销Q相反她q羃短了子类对父c静态成员的讉K
旉Q节省了子类的内存空间?BR> (6)静态数据成员在<定义或说?gt;时前面加关键字static?BR> (7)静态数据成员是静态存储的Q所以必d它进行初始化?BR> (8)静态成员初始化与一般数据成员初始化不同:
初始化在cM外进行,而前面不加staticQ以免与一般静态变量或对象相淆;
初始化时不加该成员的讉K权限控制WprivateQpublic{;
初始化时使用作用域运符来标明它所属类Q?BR> 所以我们得出静态数据成员初始化的格式:
<数据cd><cd>::<静态数据成员名>=<?gt;
(9)Z防止父类的媄响,可以在子cd义一个与父类相同的静态变量,以屏蔽父cȝ影响。这里有一炚w要注意:我们说静态成员ؓ父类和子cd享,但我们有重复定义了静态成员,q会不会引v错误呢?不会Q我们的~译器采用了一U绝妙的手法Qname-mangling 用以生成唯一的标志?BR>
Q感谢西安Y件园的王先生为我指出错误Q感谢网友backer帮助我找出正的{案。)
在光盘VC02中,在介l构造函数时Q我_“构造函数最重要的作用是创徏对象本nQ对象内存的分配由构造函数来完成的”,q句话是错的Q对象内存的分配和构造函数没有关p,对象内存的分配是q译器来完成的Q构造函数的作用是对对象本n做初始化工作Q也是l用h供初始化cM成员变量的一U方式,在类对象有虚表的情况下,构造函数还对虚表进行初始化?/P>
另外Q我_“C++又规定,如果一个类没有提供M的构造函敎ͼ则C++提供一个默认的构造函敎ͼ由C++~译器提供)”,q句话也是错误的Q正的是:
如果一个类中没有定义Q何的构造函敎ͼ那么~译器只有在以下三种情况Q才会提供默认的构造函敎ͼ
1、如果类有虚拟成员函数或者虚拟承父c(x虚拟基类Q时Q?BR>2、如果类的基cL构造函敎ͼ可以是用户定义的构造函敎ͼ或编译器提供的默认构造函敎ͼQ?BR>3、在cM的所有非静态的对象数据成员Q它们对应的cM有构造函敎ͼ可以是用户定义的构造函敎ͼ或编译器提供的默认构造函敎ͼ?/P>
――即VC++视频W三课this指针详细说明
作者:孙鑫 旉Q??xml:namespace prefix = st1 />
要更好地理解C++的多态性,我们需要弄清楚函数覆盖的调用机Ӟ因此Q首先我们介l一下函数的覆盖?/P>
我们先看一个例子: ?FONT face=Arial>1- 1 #include <iostream.h> class animal { public: void sleep() { cout<<"animal sleep"<<endl; } void breathe()
{
cout<<"animal breathe"<<endl;
}
}; class fish:public animal { public: void breathe()
{
cout<<"fish bubble"<<endl;
}
}; void main() { fish fh;
animal *pAn=&fh;
pAn->breathe();
} 注意Q在?-1的程序中没有定义虚函数。考虑一下例1-1的程序执行的l果是什么? {案是输出:animal breathe 在类fish中重写了breathe()函数Q我们可以称为函数的覆盖。在main()函数中首先定义了一个fish对象fhQ接着定义了一个指向animal的指针变量pAnQ将fh的地址赋给了指针变量pAnQ然后利用该变量调用pAn->breathe()。许多学员往往这U情况和C++的多态性搞hQ认为fh实际上是fishcȝ对象Q应该是调用fishcȝbreathe()Q输出“fish bubble”,然后l果却不是这栗下面我们从两个斚w来讲q原因?/P>
1?nbsp; ~译的角?/P>
C++~译器在~译的时候,要确定每个对象调用的函数的地址Q这UCؓ早期l定Qearly bindingQ,当我们将fishcȝ对象fh的地址赋给pAnӞC++~译器进行了cd转换Q此时C++~译器认为变量pAn保存是animal对象的地址。当在main()函数中执行pAn->breathe()Ӟ调用的当然就是animal对象的breathe函数?/P>
2?nbsp; 内存模型的角?/P>
我们l出了fish对象内存模型Q如下图所C:
?- 1 fishcd象的内存模型
我们构?FONT face="Times New Roman">fish 正如很多学员所惻I在例1-1的程序中Q我们知?FONT face="Times New Roman">pAn实际指向的是fishcȝ对象Q我们希望输出的l果是鱼的呼吸方法,卌?FONT face="Times New Roman">fishcȝbreatheҎ。这个时候,p轮到虚函数登Z?/P>
前面输出的结果是因ؓ~译器在~译的时候,已l确定了对象调用的函数的地址Q要解决q个问题p使用q绑定(late bindingQ技术。当~译器用迟l定Ӟ׃在运行时再去定对象的类型以及正的调用函数。而要让编译器采用q绑定,p在基cM声明函数时?FONT face="Times New Roman">virtual关键字(注意Q这是必ȝQ很多学员就是因为没有用虚函数而写出很多错误的例子Q,q样的函数我们称函数。一旦某个函数在基类中声明ؓvirtualQ那么在所有的zcM该函数都?FONT face="Times New Roman">virtualQ而不需要再昄的声明ؓvirtual?/P>
下面修改?FONT face="Times New Roman">1-1的代码,?FONT face="Times New Roman">animalcM?FONT face="Times New Roman">breathe()函数声明?FONT face="Times New Roman">virtualQ如下: ?FONT face=Arial>1- 2 #include <iostream.h> class animal { public: void sleep() { cout<<"animal sleep"<<endl; } virtual void breathe() { cout<<"animal breathe"<<endl; } }; class fish:public animal { public: void breathe() { cout<<"fish bubble"<<endl; } }; void main() { fish fh; animal *pAn=&fh; pAn->breathe(); } 大家可以再次q行q个E序Q你会发现结果是?FONT face="Times New Roman">fish bubble”,也就是根据对象的cd调用了正的函数?/P>
那么当我们将breathe()声明?FONT face="Times New Roman">virtualӞ在背后发生了什么呢Q?/P>
~译器在~译的时候,发现animalcM有虚函数Q此时编译器会ؓ每个包含虚函数的cdZ个虚表(即vtableQ,该表是一个一l数l,在这个数l中存放每个虚函数的地址。对于例1-2的程序,animal和fishc都包含了一个虚函数breathe()Q因此编译器会ؓq两个类都徏立一个虚表,如下图所C: ?- 2 animalcdfishcȝ虚表 那么如何定位虚表呢?~译器另外还为每个类提供了一个虚表指针(?FONT face="Times New Roman">vptrQ,q个指针指向了对象的虚表。在E序q行ӞҎ对象的类型去初始?FONT face="Times New Roman">vptrQ从而让vptr正确的指向所属类的虚表,从而在调用虚函数时Q就能够扑ֈ正确的函数。对于例1-2的程序,׃pAn实际指向的对象类型是fishQ因?FONT face="Times New Roman">vptr指向?FONT face="Times New Roman">fishcȝvtableQ当调用pAn->breathe()ӞҎ虚表中的函数地址扑ֈ的就?FONT face="Times New Roman">fishcȝbreathe()函数?/FONT> 正是׃每个对象调用的虚函数都是通过虚表指针来烦引的Q也决定了虚表指针的正初始化是非帔R要的。换句话_在虚表指针没有正初始化之前Q我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢? {案是在构造函Cq行虚表的创建和虚表指针的初始化。还记得构造函数的调用序吗,在构造子cd象时Q要先调用父cȝ构造函敎ͼ此时~译器只“看C”父c,q不知道后面是否后还有承者,它初始化父类的虚表指针,该虚表指针指向父cȝ虚表。当执行子类的构造函数时Q子cȝ虚表指针被初始化Q指向自w的虚表。对于例2-2的程序来_?FONT face="Times New Roman">fishcȝfh对象构造完毕后Q其内部的虚表指针也p初始化ؓ指向fishcȝ虚表。在cd转换后,调用pAn->breathe()Q由?FONT face="Times New Roman">pAn实际指向的是fishcȝ对象Q该对象内部的虚表指针指向的?FONT face="Times New Roman">fishcȝ虚表Q因此最l调用的?FONT face="Times New Roman">fishcȝbreathe()函数?/FONT> 要注意:对于虚函数调用来_每一个对象内部都有一个虚表指针,该虚表指针被初始化ؓ本类的虚表。所以在E序中,不管你的对象cd如何转换Q但该对象内部的虚表指针是固定的Q所以呢Q才能实现动态的对象函数调用Q这是C++多态性实现的原理?/FONT> ȝQ基cL虚函敎ͼQ?/FONT> 1?/FONT> 每一个类都有虚表?/FONT> 2?/FONT> 虚表可以l承Q如果子cL有重写虚函数Q那么子c虚表中仍然会有该函数的地址Q只不过q个地址指向的是基类的虚函数实现。如果基c?FONT face="Times New Roman">3个虚函数Q那么基cȝ虚表中就有三(虚函数地址Q,zcM会有虚表Q至有三项Q如果重写了相应的虚函数Q那么虚表中的地址׃改变Q指向自w的虚函数实现。如果派生类有自q虚函敎ͼ那么虚表中就会添加该V?/FONT> 3?/FONT> zcȝ虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列序相同?/FONT> 我在论坛?FONT face="Times New Roman">VC教学视频版面发了帖子Q是模拟MFCcd的例子写的,主要是说明在基类的构造函C保存?FONT face="Times New Roman">this指针是指向子cȝQ我们在看一下这个例子: ?- 3 #include <iostream.h>
class base;
base * pbase;
class base
{
public:
base()
{
pbase=this;
}
virtual void fn()
{
cout<<"base"<<endl;
}
};
class derived:public base
{
void fn()
{
cout<<"derived"<<endl;
}
};
derived aa;
void main()
{
pbase->fn();
} 我在basecȝ构造函C?FONT face="Times New Roman">this指针保存?FONT face="Times New Roman">pbase全局变量中。在定义全局对象aaQ即调用derived aa;Ӟ要调用基cȝ构造函敎ͼ先构造基cȝ部分Q然后是子类的部分,p两部分拼接出完整的对?FONT face="Times New Roman">aa。这?FONT face="Times New Roman">this指针指向的当然也是aa对象Q那么我?FONT face="Times New Roman">main()函数中利?FONT face="Times New Roman">pbase调用fn()Q因?FONT face="Times New Roman">pbase实际指向的是aa对象Q?FONT face="Times New Roman">aa对象内部的虚表指针指向的是自w的虚表Q最l调用的当然?FONT face="Times New Roman">derivedcM?FONT face="Times New Roman">fn()函数?/FONT> 在这个例子中Q由于我的疏忽,?FONT face="Times New Roman">derivedcM声明fn()函数Ӟ忘了?FONT face="Times New Roman">public关键字,D声明ZprivateQ默认ؓprivateQ,但通过前面我们所讲述的虚函数调用机制Q我们也明白了q个地方q不影响它输出正的l果。不知道q算不算C++的一?FONT face="Times New Roman">BugQ因函数的调用是在运行时定调用哪一个函敎ͼ所以编译器在编译时Qƈ不知?FONT face="Times New Roman">pbase指向的是aa对象Q所以导致这个奇怪现象的发生。如果你直接?FONT face="Times New Roman">aa对象去调用,׃对象cd是确定的Q注?FONT face="Times New Roman">aa是对象变量,不是指针变量Q,~译器往往会采用早期绑定,在编译时定调用的函敎ͼ于是׃发现fn()是私有的Q不能直接调用。:Q?/FONT> 许多学员在写q个例子Ӟ直接在基cȝ构造函C调用虚函敎ͼ前面已经说了Q在调用基类的构造函数时Q编译器只“看C”父c,q不知道后面是否后还有承者,它只是初始化父类的虚表指针,让该虚表指针指向父类的虚表,所以你看到l果当然不正。只有在子类的构造函数调用完毕后Q整个虚表才构徏完毕Q此时才能真正应?FONT face="Times New Roman">C++的多态性?B>换句话说Q我们不要在构造函C去调用虚函数Q当然如果你只是惌用本cȝ函数Q也无所谓?/B>
1、文章《在VC6.0中虚函数的实现方法》,作者:backer Q网址Q?/FONT> http://www.mybole.com.cn/bbs/dispbbs.asp?boardid=4&id=1012&star=1 2、书?FONT face="Times New Roman">C++~程思想?FONT face="Times New Roman"> 机械工业出版C?/FONT> 本想再写详细些,发现旉不够QL有很多事情,在加上水q也有限Q想惌是以后再说吧。不q我怿Q这些内容也能够帮助大家很好的理解了。也Ƣ迎|友能够l箋补充Q大家可以鼓动鼓?FONT face="Times New Roman">backerQ让他从汇编的角度再l一个说明,哈哈Q别说我说的?/FONT>1. 函数的覆?/H1>
2. 多态性和虚函?/H1>
3. VC视频W三?FONT face="Times New Roman">this指针说明
4. 参考资料:
5. 后记