侯捷老师的深入浅出MFC里有一点,我想了半天才想明白!现在记录一下思路!
如同图上画红色图线的那个函数!(DWORD) (*m_pStoreMap)[ (void *)pClassRef ],这个东西看了好久,愣是没有看懂!不过后来看了一下CArchive中关于m_pStoreMap的定义,如下:
看见没,m_pStoreMap是一个CMapPtrToPtr类型的指针,这个类可能大家比较陌生,我在这里稍微查了一些资料,描述如下:
会用CMap了,CMapPtrToPtr也会用了,很容易理解。
映射表类(CMap)是MFC集合类中的一个模板类,也称作为“字典”,就像一种只有两列的表格,一列是关键字,一列是数据项,它们是一一对应的。关键字是唯一的,给出一个关键字,映射表类会很快找到对应的数据项。映射表的查找是以哈希表的方式进行的,因此在映射表中查找数值项的速度很快。举个例子来说吧,公司的所有职员都有一个工号和自己的姓名,工号就是姓名的关键字,给出一个工号,就可以很快的找到相应的姓名。映射类最适用于需要根据关键字进行快速检索的场合。
CMapWordToPtr 保存void指针,关键字为WORD
CMapPtrToWord 保存WORD,关键字为void指针
CMapPtrToPtr 保存void指针,关键字为其它void指针
CMapWordToOb 保存CObject指针,关键字为WORD
CMapStringToOb 保存CObject指针,关键字为字符串
CMapStringToPtr 保存void指针,关键字为字符串
CMapStringToString 保存字符串,关键字为字符串
因此,m_pStoreMap其实是指向一个映射表的指针!这个映射表键值(key)是(void *)类型的,同时其值(value)也是(void*)类型的。当然可能你会问为什么上面的m_pStoreMap要定义成枚举类型,好吧!这与现在的问题无关,我们等会再说!
对于这个式子,我们慢慢分解,(DWORD) (*m_pStoreMap)[ (void *)pClassRef ],由于pClassRef是CRuntimeClass*类型的,所以自然而然,前面的(void*)起到了强制转换的作用,根据我们对于指针的理解,自然(*m_pStoreMap)代表了一个CMapPtrToPtr类型的首地址,CMapPtrToPtr复写了[]运算符,现在脉络就很清晰了,(*m_pStoreMap)[ (void *)pClassRef ]表示从(*m_pStoreMap)这个映射表里找到键值为 (void *)pClassRef 的对应的值(value),自然这个值也是(void*)类型,所以在前面要加上强制转换符(DWORD)。
这个式子算是理解清楚了,我其实一直不明白为什么要这样弄?
侯捷老师的书这章我看了几遍,还是没弄懂,直到最近用vs调试了一下,才明白了意图!
假设我们现在是画完了一幅图,准备保存,好,我现在在CArchive::WriteClass这个函数里面设了一个断点!
上面的那一些函数调用我们都不管,我们看关键的几步!
好吧,至少现在这个函数(MapObject)什么都没做!我断点运行了一遍!但是这确实是很重要的一个函数,我在后面会提及!现在大家可以不管!
(在这里这个函数的作用是初始化映射表,如果m_pStoreMap为NULL,就创建一个CMapPtrToPtr给m_pStoreMap,并且在里面就已经放入了一个初始值!NULL对应(void*)(DWORD_PTR)wNullTag,当然wNullTag被宏定义为0,并将m_nMapCount 赋为1)
接下来继续运行,也就是运行到(DWORD) (*m_pStoreMap)[ (void *)pClassRef ]这一句了,很明显,我们并未有在里面存储索引值(void *)pClassRef的值,查阅MSDN,我知道了,如果(*m_pStoreMap)里面没有这个索引的话,会返回void,也就是0,这个if不成立,运行到else处,我截一下代码!
侯捷老师的书里面丢了一句我认为比较重要的句子没写,那就是我上面画横线的一句,
(*m_pStoreMap)[(void*)pClassRef]=(void*)(DWORD_PTR)m_nMapCount++;
这一句的意思很明显了,那就是将值存进去,也就是如果是一个新类的话,就将键与值存入映射表,千万别忘了那些个类里面的CRuntimeClass可是static类型,也就是同一个类的CRuntimeClass*都是一个常值,这里也就建立了关系!
好吧!下一次如果我要存一个相同的类,那么运行至(DWORD) (*m_pStoreMap)[ (void *)pClassRef ]这一句的时候就会读出一个不为0的值了!
现在与侯捷老师说的就联系起来了!噢耶!
再普及一个知识点!你看侯捷后面写了几个类的时候是不是很奇怪?为什么存第二个新类的时候,索引值(nClassIndex)变成了3,第三个新类是变成了5,我们回过头来看看调用WriteClass函数的WriteObject就知道了。
备注:
1.看到没,调用完WriteClass之后,WriteObject立马又把pOb也就是传进来的类指针这个key和对应的value加到Map里面去了,并且m_nMapCount++,第一次存完新类之后,这个值就变成了3!
2.也就是说,每一次存储新类的时候都要存两个,一个是pOb->GettimeClass()和pOb本身,后面的m_nMapCount一共加2次!
3.顺便提两句,m_pStoreMap是在这里被赋值的,这里完成m_pStoreMap的初始化工作,先new一个CMapPtrToPtr,然后赋给m_pStoreMap,然后往里面存一个初始的值!并将m_nMapCount置为1.在后面的WriteClass里面虽然也调用MapObject(NULL),但是由于已经初始化了,那里面基本上什么也没做!好了,解释得已经比较清楚了!噢耶!
Ps:我用的是VS2012模拟的,其实变化也不是很大!有一些地方与书上不同,但是大体一致!
至于为什么上面的m_pStoreMap要定义成枚举类型,我来解释一下:
union
{
CPtrArray* m_pLoadArray;
CMapPtrToPtr* m_pStoreMap;
};
我们知道CArchive只有有两个“状态”,存储和读取,用以个union分别表示不同状态时的成员:
CPtrArray* m_pLoadArray; //Store时不使用
CMapPtrToPtr* m_pStoreMap; //Load时不使用
共用类型的唯一好处就是节约内存!既然两个变量在任意时刻只使用一种,那么自然,就可以两个变量共享同一块内存!
博客里面的图片不够清晰,我给大家一个doc文档吧!
http://pan.baidu.com/s/1hqhyQ1Y