小憩,味一二 ——08年3月编程手札
生活随笔
收集整理的這篇文章主要介紹了
小憩,味一二 ——08年3月编程手札
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
日前筆者在VC6.0開發環境中基于MFC類庫開發實現了一個簡單的功能,其中在定制派生于CTreeCtrl類時利用到了集合類CPtrArray,編寫時遇到若干有趣點,特記錄于此。 1開發情境:程序大致功能是初始化時從User.ini文件中讀出用戶所屬部門和姓名顯示在樹形控件中,當用戶在使用程序中展開節點,獲取葉子節點用戶的基本資料,而基本資料的獲取有賴于所屬部門和姓名,當決定如何獲取用戶資料時,利用到了CPtrArray。 User.ini結構中存儲的數據形式是: [1] dep=部門 name=姓名 face=頭像位圖存放地址 onip=在線IP地址 [2] dep=部門 name=姓名 face=頭像位圖存放地址 onip=在線IP地址字段分別以整數表示,即從1到n,n表示總共用戶數,鍵和鍵值如上表示。 2實現方法一:方法思路是利用結構體和CPtrArray在初始化時就提取出用戶所有資料保存到結構體和CPtrArray中,當需要獲取葉子節點用戶基本資料時從CPtrArray中導出即可,而不再需要訪問User.ini加快處理速度。所定義結構體聲明如下: typedef struct _UserInfo { ?????? char? chDep[25];//部室 ??? char? chName[20];//真實姓名 ?????? char? chIP[20];//IP ?????? char? chFace[10];//頭像 }UserInfo,*PUserInfo; 2.1錯誤的實現一所定制的類CCustomeTreeCtrl派生于CTreeCtrl類,首先在CustomeTreeCtrl.h中定義CPtrArray類的變量: CPtrArray m_pPtrArray; 在CustomeTreeCtrl.cpp中實現函數如下: void CCustomeTreeCtrl::InitTreeCtrl( ) { ?????? int iIndex=1; ?????? CString strPath=GetModulePath();//獲取路徑,該函數是自己完成,這里直接調用 ?????? CString strPath1=strPath+" //user.ini"; CString strField; CString strUserDep; CString strUserName; CString strUserONIP; CString strUserFace; ?????? while(1) ?????? { ????????????? strField.Format("%d",iIndex); ????????????? GetPrivateProfileString(strField,"dep","NULL", ????????????? ?????????????? strUserDep.GetBuffer(30),30,strPath1);//獲取部門 ????????????? strUserDep.ReleaseBuffer(); ????????????? if("NULL"==strUserDep) break;//循環結束 ????????????? GetPrivateProfileString(strField,"name","NULL", ????????????? ?????????????? strUserName.GetBuffer(30),30,strPath1);//獲取姓名 ????????????? strUserName.ReleaseBuffer(); ??????? //顯示到樹形控件中的節點,這里代碼作了節略。 ????????????? GetPrivateProfileString(strField,"onip","NULL", ????????????? ?????????????? strUserONIP.GetBuffer(30),30,strPath1);//獲取IP ????????????? strUserONIP.ReleaseBuffer(); ????????????? GetPrivateProfileString(strField,"face","NULL", ????????????? strUserFace.GetBuffer(30),30,strPath1);//獲取頭像位圖存放位置 ????????????? strUserFace.ReleaseBuffer(); ????????????? //存儲到結構體 ????????????? UserInfo userInfo; ??????? memset(&userInfo,0,sizeof(UserInfo)); strcpy(userInfo .chDep,strUserDep); strcpy(userInfo .chName,strName); ??????? strcpy(userInfo .chIP,strUserONIP); strcpy(userInfo .chFace,strUserFace); //加到集合類中 m_pPtrArray.Add(&userInfo); iIndex++; ?????? } } 上面這個函數實現了從User.ini中提取所有用戶的資料,存在到結構體UserInfo和m_pPtrArray中,下面函數實現從m_pPtrArray提取所需要的用戶資料。 void CCustomeTreeCtrl::GetInfoFromArray(UserInfo *pUserInfo) { ?????? //采用傳址方式實現 //獲取從樹控件葉子節點傳遞進來的部門和姓名值,放在pUserInfo中 ??? //根據部門和姓名從集合類中獲取對應的用戶其他資料,放在pUserInfo中 int iCount=m_pPtrArray.GetSize();//獲取記錄總數 for(int i=0;i<iCount;i++){ ?????? UserInfo *pUserInfoArray=m_pPtrArray.GetAt(i); ??? if(!strcmp(pUserInfo->chDep,pUserInfoArray->chDep) && ????? !strcmp(pUserInfo->chName,pUserInfoArray->chName) ) {//相等,是該用戶 ?????? strcpy(pUserInfo->chIP,pUserInfoArray-> chIP); strcpy(pUserInfo->chFace,pUserInfoArray-> chFace); //得到了該用戶資料,退出循環 break; } } 這兩個函數從邏輯上看并無問題,編譯自然也沒有任何語法錯誤,那么錯在那呢?因為在測試過程中很快就發現了這個問題,對于這類可重建的錯誤還是可以通過單步調試發現問題根源。調試發現UserInfo *pUserInfoArray=m_pPtrArray.GetAt(i);所獲取的結構體UserInfo中值是亂碼,這是為什么呢?也就是說值沒有放到集合類pPtrArray中!這里就要對CPtrArray有所了解了,CPtrArray所存儲的是指針變量,就是結構體UserInfo的地址。CPtrArray只負責把相應的指針體串聯起來形成一個隊列。當把結構體UserInfo聲明為局部變量,只在循環體存在生命周期,在另一個函數中就已經不存在為其分配的內存地址,也就是棧中本分配給該結構體的地址空間另作它用了,而m_pPtrArray仍然存儲這個地址,顯然獲取的就是亂碼了。 2.2錯誤實現二有了上面的錯誤,解決辦法最直接的就是把結構體UserInfo所生命的變量生命周期延長與對象一般,在CustomeTreeCtrl.h中定義: UserInfo?? m_UserInfo; 在函數中InitTreeCtrl( )和GetInfoFromArray(UserInfo *pUserInfo)中是作用范圍。但這時出現了想當然的錯誤。代碼顯示的結果是無論UserInfo *pUserInfoArray=m_pPtrArray.GetAt(i)中i如何變化,顯示的都是同一個用戶資料,就是在函數InitTreeCtrl( )中獲取到的最后一個用戶資料。略思即明了,CPtrArray串聯起來的是地址,而這里只聲明了一個變量,也就是CPtrArray串聯了都是同一個地址,這樣這個變量盡管作用域擴大了,但每次都被覆蓋重寫,然后連到m_pPtrArray。這兩錯誤反應出兩個基本問題:編程時輕易會產生的錯誤就是想當然的邏輯;同時在測試中對于錯誤實現二而言存在一個測試度的問題,大量而恰如其分的測試是必須有清晰思路的,除了對功能和業務相關性的把握上,還需要對代碼實現的技術上有所側重,這樣才能保證測試的高效。 2.3正確的實現一要正確實現,很明顯在在CustomeTreeCtrl.h中定義結構體UserInfo為數組。筆者測試時,聲明了UserInfo?? m_UserInfo[10];結果發現正確顯示了。但這個實現是不理想的,首先數組聲明的量該多少呢?也就是該預先分配多少個結構體UserInfo的地址空間來存放用戶呢?用戶的總數目前還是未知的,這個時候一般思路是往大了分配,但這樣肯定是浪費了空間。如果先運行一次循環獲取用戶總數在分配對應空間,這需要再進行一次n的線性循環,也浪費了時間。 2.4正確的實現二既然在棧中分配空間不理想,這個實現就用到了堆中分配具體實現是:這是錯誤實現一函數InitTreeCtrl( )中代碼: //存儲到結構體 UserInfo userInfo; ??????? memset(&userInfo,0,sizeof(UserInfo)); strcpy(userInfo .chDep,strUserDep); strcpy(userInfo .chName,strName); ??????? strcpy(userInfo .chIP,strUserONIP); strcpy(userInfo .chFace,strUserFace); //加到集合類中 m_pPtrArray.Add(&userInfo); 把這段代碼改成: //存儲到結構體 UserInfo *pUserInfo=new UserInfo;//堆中分配 ??????? memset(pUserInfo,0,sizeof(UserInfo)); strcpy(pUserInfo->chDep,strUserDep); strcpy(pUserInfo->chName,strName); ??????? strcpy(pUserInfo->chIP,strUserONIP); strcpy(pUserInfo->chFace,strUserFace); //加到集合類中 m_pPtrArray.Add(pUserInfo); 堆中所動態分配的空間,如果不顯示刪除,即調用delete,其生命周期和應用程序一樣。測試結果正常。這樣避免了浪費空間和增加一次n的循環。這也體現了堆棧分配空間的區別。但這樣同樣會造成內存泄露的可能性,同時如果用戶數量過大,對內存空間來說是個不小的負擔。 3實現方法二:算法無非體現在時間和空間效率上,既然在空間上不滿意上面的實現,可著眼于時間效率上,也就是犧牲時間換取空間。這個實現就是在根據葉子節點上部門和姓名去提取用戶資料時,還是從User.ini中取,不再內存中分配空間來存儲。具體實現如下:在CustomeTreeCtrl.cpp中實現函數如下: void CCustomeTreeCtrl::InitTreeCtrl( ) { ?????? int iIndex=1; ?????? CString strPath=GetModulePath();//獲取路徑,該函數是自己完成,這里直接調用 ?????? CString strPath1=strPath+" //user.ini"; CString strField; CString strUserDep; CString strUserName; ?????? while(1) ?????? { ????????????? strField.Format("%d",iIndex); ????????????? GetPrivateProfileString(strField,"dep","NULL", ????????????? ?????????????? strUserDep.GetBuffer(30),30,strPath1);//獲取部門 ????????????? strUserDep.ReleaseBuffer(); ????????????? if("NULL"==strUserDep) break;//循環結束 ????????????? GetPrivateProfileString(strField,"name","NULL", ????????????? ?????????????? strUserName.GetBuffer(30),30,strPath1);//獲取姓名 ????????????? strUserName.ReleaseBuffer(); ????????????? //顯示到樹形控件中的節點,這里代碼作了節略。 iIndex++; ?????? } } 上面這個函數實現了從User.ini中僅提取姓名和部門顯示在樹形控件中節點。 void CCustomeTreeCtrl::GetInfoFromUserIni(UserInfo *pUserInfo) { ?????? BOOL flag=TRUE; ?????? int iIndex=1; ?????? CString strPath=GetModulePath(); ?????? CString strPath1=strPath+" //user.ini"; ?????? while(flag) ?????? { ????????????? CString strField; ????????????? strField.Format("%d",iIndex); ????????????? CString strUserDep; ????????????? CString strUserName; ????????????? GetPrivateProfileString(strField,"dep","NULL", ????????????? ?????????????? strUserDep.GetBuffer(30),30,strPath1); ????????????? strUserDep.ReleaseBuffer(); ????????????? if("NULL"==strUserDep) break; ????????????? GetPrivateProfileString(strField,"name","NULL", ????????????? ?????????????? strUserName.GetBuffer(30),30,strPath1); ????????????? strUserName.ReleaseBuffer(); if((CString)pUserInfo->chDep==strUserDep&& (CString)pUserInfo->chName==strUserName)//判斷是否是該用戶的資料 ????????????? { ???????????????????? CString strUserONIP; ???????????????????? CString strUserFace; ???????????????????? GetPrivateProfileString(strField,"onip","NULL", ????????????? ?????????????? strUserONIP.GetBuffer(30),30,strPath1); ???????????????????? strUserONIP.ReleaseBuffer(); ???????????????????? strcpy(pUserInfo->chIP,strUserONIP); ???????????????????? GetPrivateProfileString(strField,"face","NULL", ????????????? ?????????????? strUserFace.GetBuffer(30),30,strPath1); ???????????????????? strUserFace.ReleaseBuffer(); ???????????????????? strcpy(pUserInfo->chFace,strUserFace); ???????????????????? break; ????????????? } ????????????? iIndex++; ?????? } } 函數GetInfoFromUserIni(UserInfo *pUserInfo)是從文件User.ini中直接在提取。 4兩種方法比較 1)? 代碼執行行數實現方法二執行代碼的行數明顯減少了,體現在函數GetInfoFromUserIni(UserInfo *pUserInfo)中if語句中代碼只執行一次。而在實現方法一中函數InitTreeCtrl( )卻要都執行,也就是說用戶的資料選擇一個節點只需要提取一個用戶的資料,實現方法二是這樣,但實現方法一卻提取了所有用戶資料在那里等待。 2)? 空間地址分配實現方法二不需要任何額外的內存,實現方法一則需要n個用戶的結構體UserInfo空間。實現方法一是把用戶資料先放到內存等待提取,而實現方法二是需要用戶資料時再到文件中提取。 3)? 循環實現效率兩種方法都需要兩次n的循環,不同的是循環所提取的資料是從不同地方而來,實現方法一是直接在內存中比較循環,而實現方法二是從文件中提取。 4)? 綜合比較盡管實現方法二在空間和執行代碼行數上減少了不少時間,但從文件中提取數據也犧牲了不少時間。而實現方法一除了只做一次文件數據提取可以在時間上優勝外,空間和代碼執行行數上卻是極大的缺陷。綜上,本次程序編寫總結有下面三點值得玩味:測試的度;堆棧的選擇;時空效率,犧牲時間或犧牲空間應根據具體而定。 ? ? 08.03.10
總結
以上是生活随笔為你收集整理的小憩,味一二 ——08年3月编程手札的全部內容,希望文章能夠幫你解決所遇到的問題。