⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 13.5 文档对象数据的销毁.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 TXT
字号:
13.5 文档对象数据的销毁

本章上述内容实现的Graphic程序,还隐含着一个错误,当新建→个文档对象时,或者打开一个文档
对象时,先前文档所保存的数据没有被销毁,这主要是指 CGraphicView 类OnLButtonUp函数中在堆
上为CGraph对象分配的内存(即下面这条语句调用分配的内存)没有被释放。 
CGraph *pGraph~new CGraph(~nDrawType,m-ptOrigin,point); 
当我们新建一个文档时,程序文档对象所保存的数据要被销毁,然后再与一个新的文档相关联。然
而对于在堆上分配的内存,必须由程序员自己去释放。我们可以看一下 CDocument类的 
OnNewDocument函数的实现,其源代码位于 DOCCORE.CPP中。 
例13-29 

BOOL CDocument::OnNewDocument() 

{ 

if(工sModified())

TRACEO ("Warning: OnNewDocument replaces an unsaved document.\丑 11 ) ; 
DeleteContents() ;  
m_strPathName.Empty();  11  no  path name  yet  
SetModifiedFlag(FALSE);  11  make clean  

return TRUE; 

可以看到, CDocument类OnNewDocument函数的默认实现是调用CDocument类的另一个成员函数 
DeleteContents,以确保这个文档是空的,然后标记该新文档是干净的。同样的,当单击【文件\
打开】菜单命令后,程序框架会调用OnOpenDocument函数,其默认实现是打开指定的文件,调用 
DeleteContents函数以确保这个文档是空的。也就是说,不管是新建文档,还是打开一个己有文档,
都是先删除文档数据,因为本例是单文档应用类型的程序,它只有一个文档对象,该对象将被重复
使用,所以应该在该文档对象被再次使用之前去删除和这个文档对象相关联的所有数据。因为文件
打开和文件新建都会调用 DeleteContents函数,所以在这个函数中释放文档对象在堆上分配的内存
是比较合适的。 DeleteContents画数是一个虚函数,主要是由框架调用,用来删除文档的数据,同
时并不销
毁CDocument对象本身a它是在文档将要被销毁之前被调用,它


使用之前被调用,以确保文档是空的。对单文档应用程序来说,这一点特别重要,因为仅仅使用一
个文档,无论用户是创建,还是打开另一个文档,该文档对象都是被重复使用的。所以在文档对象
被重复使用之前,应释放己分配的内存。
对Graphic程序来说,在DeleteContents函数被调用时就应该释放在堆上分配的CGraph 
创.~ I 519 

第13


这个对象的内存。于是,我们为 CGraphicDoc类增加虚函数: DeleteContents的重载,然后在其中
添加如例 13-30所示代码。 
1J~ 13-30 

1. void CGraphicDoc::DeleteContents() 

2. { 

3. // TODO: Add your specialized code here and/or call the base class 


4. int nCount i 

5. nCount=m_obArray.GetSize() i 

6. for(int i=Oii<m一obArray.GetSize()ii++) 

7. { 


8. delete m一obArray.GetAt(i) i 
9. m_obArray.RemoveAt(i) i 

10. } 


11. 
12. CDocument::DeleteContents(); 

13. } 


在上述如例 13-30所示代码中,首先定义了一个整型变量: nCount,保存m_obArray数组中的元素个
数。因为先前利用new操作符分配的内存,所以必须利用delete函数释放该内存。上述例 13-30所示
代码中通过for循环遍历m_obArray数组中的每一个元素,并利用 CObArray类的 GetAt成员函数取出
指定索引的元素。因为在CObArray数组中保存的元素都是指针,所以利用delete函数将删除这个指
针所指向的堆内存。读者一定要注意,虽然这时删除了这个指针所指向的堆内存,但是对于m_obArray
数组所保存的元素来说,其内存并没有被删除,也就是说,它所保存的指针值还是存在的,所以我
们需要把数组所保存的元素,即 CGraph指针值删除掉。 CObArray类中有-个成员函数: RemoveAt,
可以删除指定索引处的元素,其原型声明如下所示: 
void RemoveAt( int nlndex, int nCount = 1 )i 
该函数的第一个参数(nIndex)设定索引,第二个参数 (nCount)指定要移走的元素数目。因此,在如
例 13-30所示代码中,在释放堆内存之后,就调用RemoveAt函数删除指定索引i处的元素。
我们在上述例 13-30所示代码中 for循环处设置一个断点,调试运行程序,利用相应绘图菜单命令
绘制一些图形,例如绘制三条直线,即这时会产生三个图形对象(/IP CGraph 对象)。我们知道,当
新建文件、打开文件,或者关闭程序时,也就是说,当文档对象被销毁时,都会去调用文档类对象(本
例即CGraphicDoc对象)的DeleteContents函数。因此,我们执行关闭 Graphic程序的操作,程序将
进入上述例 13-30所示的 DeleteContents函数中。读者将可以看到这时 nCount变量的值等于 3,
这个数值是正确的,因为此时 m_obArray数组中确实是保存了三个元素。然后单步执行程序,这时
索引 i是 0,也就是释放索引0位置处的元素所在的堆内存(第8行代码),然后移走元素本身(第9行
代码),这是第一次循环。继续运行,进入第二次循环,此时i为1,所以删除索引为 1的元素所
. 

520 I ~~势

指向的堆内存(第 8行代码〉、并移走元素本身(第 9行代码)。继续运行,接下来应释放索引为 2
元素所指的堆内存,但是发现程序直接退出了,并没有进入第 3次循环。
这里之所有没有成功删除内存,主要是因为利用 RemoveAt函数删除元素时出现了问题,该函数是在
数组指定的索引位置处开始移走一个或多个元素,在这个过程中,它会下移在这个元素之上的所有
元素,并减少这个数组的上界。也就是说. RemoveAt函数的调用会导致数组中剩余元素的重新排列。
例如,数组有三个元素,当移走索引为 O的元素时,原先索引为 1和 2的元素都会下移,即索引 1
的元素现在索引为 O.索引 2的元素现在索引为 1。因此在上述代码中,第二次进入循环时, i已经
变成 1了,于是它删除的实际是原先索引为 2的那个元素,即数组中的最后一个元素,但是删除这
个元素之后,还漏掉了一个元素,即原索引为1,现索引为 O的那个元素。也就说它循环两次之后就
出现问题了。这是我们经常容易犯的错误,把判断元素数目的代码放置在条件判断的位置(上述代码
中 for循环的条件判断语句: i< m_obArray.GetSizeO).对于刚才这种写法,因为删除元素后,它的
大小就会发生变化. GetSizeO函数的返回值也在不断变化,原先是 3个元素,移走一个后变成 2个。
所以第三次循环时,索引 i是 2.而 GetSize函数的返回值是1,所以循环结束。而这样的一种调用,
程序员根本发现不了它有问题。当我们不断地新建文档和打开另一个文档时,实际上就隐含的有内
存泄漏的发生,因为有些对象的内存没有被释放。如果我们对 RemoveAt函数调用的机制不太了解的
话,所编写的代码就会存在内存泄漏的隐患,因此在这里需要修改代码,在释放元素所保存的指针
所指向的堆内存之后(第 8行代码).先不删除这个元素本身,然后在 for循环结束之后,也就是所有
元素所指向的堆内存都被删除之后,再删除这些元素。为了删除 m_obArray数组中的所有元素,并
不需要再进行一次循环,逐一删除,因为 CObArray类中还有一个成员函数: RemoveAll.用于从这个
数组中移除所有元素。也就是说,在上述 for循环之后,可以调用 m_obArray对象的 RemoveAll函
数,删除其所有元素。这样的话,程序就不会出现问题了。修改后的代码如例 13-31所示。 
例 13-31 

void CGraphicDoc::DeleteContents() 
// TODO: Add your specialized code here and/or call the base class int nCount; 
nCount=m一obArray.GetSize(); 
for(int i=O;i<nCount;i++) 
delete m_obArray.GetAt(i); 

//m_obArray.RemoveAt(土) ; 
m_obArray.RemoveAll(); 
CDocument::DeleteContents(); 
m 
这时,读者可以再次调试运行 Graphic程序,同样也绘制三条直线,然后执行关闭程

13 

序的操作,程序将进入如例 13-31所示 CGraphicDoc类的 DeleteContents函数中,继续调试程序,
将会看到 DeleteContents函数中的 for循环确实执行了三次,把程序中分配的堆内存都释放掉了,
最后移走了数组中的所有元素。
另外,我们还可以采用另一种实现方式,可以从索引最大的元素开始删除,这时的实现代码如例 
13-32所示。
例 13-32 

void CGraphicDoc: :DeleteContents() 
{ 
// TODO: Add your specialized code here and/or call the base class 
int nCount; 
nCount=m一obArray.GetSize() ; 
while(nCount--) 
{ 
delete m_obArray.GetAt(nCount); 
m一 obArray.RemoveAt(nCount); 
CDocument: :DeleteContents(); 
在上述例 13-32代码中,得到数组元素大小后,进行 while循环,在此循环中,删除当前索引位置
处元素保存的指针所指向的堆内存。当然,这时就可以调用 RemoveAt函数删除数组索引值最大的那
个元素。我们可以分析一下这时程序的调用流程,首先 nCount等于 3,进入 while循环,先判断条
件为真,然后 nCount值减 1。于是移走 m_obArray数组中索引为 2的那个元素保存的指针所指向的
堆内存,并删除元素本身:再进入 while语句,先判断 nCount值为 2,条件为真,继续循环, nCount
值减 1变成1,释放索引 1位置处的元素保存的指针所指向的堆内存,并删除该元素本身:接下来,
因为 nCount值为1, while语旬的条件仍为真,继续循环, nCount值减 1变为 0,释放索引 0位置
处的元素保存的指针所指向的堆内存,并删除该元素本身:这时,因为 nCount值为 0, while条件
为假,所以 while循环终止。可见,当绘制三条直线时,上述 while循环确实执行了三次。读者可
以自行调试运行 Graphic程序,测试这段代码。 


⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -