練習一
以下函數完全沒有檢查可能的數據錯誤以及可能的執行失敗。請指出此函數中所有可能發生錯誤的地方。本題并不考慮出現異常。
int *alloc_and_init(string file_name)
{
ifstream infile(file_name);
int elem_cnt;
infile >> elem_cnt;
int *pi = allocate_array(elem_cnt);
int elem;
int index = 0;
while(infile >> elem)
{
pi[index++] = elem;
}
sort_array(pi, elem_cnt);
register_data(pi);
return pi;
}
這是書中第203頁的練習7.1。
我自己的答案:打開文件后,未對infile進行判斷,是否打開文件成功;pi是否分配成功,未進行判斷,它是否為null。
侯捷老師給的答案如下:
第一個錯誤便是“型別不符”。ifstream constructor 接受的參數型別是const char*而非string。這個沒有注意到。解決方法是利用string的c_str member function取得其c-style字符串表現形式:
ifstream infile(file_name.c_str());
第二個錯誤是檢查infile是否成功開啟。
if ( !infile ) // 開啟失敗
第三個錯誤就是infile >> elem_cnt 可能執行失敗。
如,文件內含的是文字,那么企圖“讀入某個數值并置于elem_cnt內”的操作便告失敗。此外,文件也有可能是空的。必須檢查讀取是否成功。
infile >> elem_cnt;
if (! infile) // 讀取失敗
第四個錯誤int *pi = allocate_array(elem_cnt);
無論何時,當我們處理指針時,必須隨時注意指針是否的確指向實際存在的對象。如果allocate_array()無法配置足夠內存,pi便會被設為0,我們必須檢驗如下:
if ( ! pi ) // allocate_array() 沒有配置到內存
需要說明的是:程序的假設是(1)elem_cnt代表文件中的元素個數;(2)數組索引值index絕不會發生溢出。但是除非我們檢查,否則實在無法保證index永遠不大于elem_cnt。
第一個錯誤和第三個錯誤,沒有考慮到。分析看,對于“型別不符”這個問題,一直沒有注意到。此外,對于讀入文字,沒有思考那么多。
努力學習ing……
posted @
2008-11-03 13:35 Sandy 閱讀(353) |
評論 (0) |
編輯 收藏
什么是內存泄露
內存泄露是一種實現錯誤。它將會慢慢耗盡系統內存空間。當計算機運行進程的時候,可能需要或多或少的內存。這主要以來于進程每時每刻將要執行的命令。當進程需要更多的內存時,將給操作系統提出請求。當進程不再需要內存的時候,將會把內存釋放掉,還給操作系統。這樣其他進程才可以使用。如果進程沒有正確的將內存還給操作系統,盡管它不再使用,但是內存的狀態仍然是不可再分配。這將減少可用內存。
一般我們常說的內存泄露是指堆內存的泄露。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完后必須釋放的內存。應用程序一般使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完后,程序必須負責相應得調用free或delete釋放該內存塊,否則,這塊內存就不能再次被使用,我們就說這塊內存泄露了。
對于一些界面的資源,如window的創建,menu的創建,dc的創建等等,對于這些,我們需要在不使用它們的時候,調用相應的函數,進行釋放。否則也將會造成內存泄露。
內存泄露的后果
內存泄露會因為減少可用內存的數量從而降低計算機的性能。最終,在最糟糕的情況下,過多的可用內存被分配掉導致全部或部分設備停止正常工作,或者應用程序崩潰。
內存泄露可能不嚴重,甚至能夠被常規的手段檢測出來。在現代操作系統中,一個應用程序使用的常規內存在程序終止時被釋放。這表示一個短暫的應用程序中的內存泄露不會導致嚴重后果。
在以下情況下,內存泄露導致較嚴重的后果:
一是程序運行后置之不理,并且隨著時間的流失消耗越來越多的內存(比如服務器上的后臺任務,尤其是嵌入式系統中的后臺任務,這些任務可能被運行后很多年內都置之不理);
二是新的內存被頻繁的分配,比如當現實電腦游戲或動畫視頻畫面時;
三是程序能夠請求未被釋放的內存(比如共享內存),甚至是程序終止的時候;
四是泄露在操作系統內部發生;
五是泄露在系統關鍵驅動中發生;
六是內存非常有限,比如在嵌入式系統或便攜設備中
七是當運行于一個終止時內存并不自動釋放的操作系統治上,而且一旦丟失,只能通過重啟來恢復。
在這里,我著重強調一下嵌入式系統,由于系統內存非常有限,編寫應用程序的時候,一定要防止內存泄露的發生。如果發生,可能是會使你編寫的應用程序出現異常。或者你的系統的性能將會大大降低。甚至有時你不得不依靠重起系統來恢復。
內存泄露的檢測
檢測內存泄露的工具:debugnew
http://dev.csdn.net/article/58/58407.shtm
淺談內存泄漏(二)
http://www.vczx.com/article/show.php?id=68
一個跨平臺的 C++ 內存泄漏檢測器
http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html
內存泄露檢測
http://www.shnenglu.com/Ipedo/archive/2005/10/27/867.aspx
posted @
2008-11-02 10:50 Sandy 閱讀(375) |
評論 (0) |
編輯 收藏
提問的智慧 - How To Ask Questions The Smart Way
by Freesc Huang
http://fox23.cnblogs.com/
在各種技術社區,論壇和郵件組,用戶和開發者們總是期望著自己能夠獲得幫助。在本文之前,曾經有過一些經典的文章,比如由Eric S. Raymond 和Rick Moen 撰寫的How to Ask Questions the Smart Way --- 我得承認我盜用了這個文章的標題:)
不過在我所關注的一些技術社區內,還是有一些朋友經常華麗地忽視掉一些細節,盡管某些問題實際上對提問者本身沒有什么關系,但是他們確實影響了看到這個問題的其他人的思路和感受。當然,我必須得承認,當寫下本文之前,曾經我自己的某些提問可能也會有類似的問題:
注意社區板塊分類
在您發布一個主題的時候,請先確保您所發布的主題的確是和該板塊的定位相符的。一般的論壇板塊應該都不是按板塊1,板塊2,板塊N來命名的,請您盡量先找到合適的板塊再提問。對于概要的問題不必放在細節的板塊中去討論,比如有關.Net Framework運行機制的討論就不益放在Visual C#的板塊中,而應該去.Net Framework的板塊討論。
標題格式
請您在標題中盡量提供一些問題相關描述,比如你有疑問的控件名稱,出現的錯誤信息,使用的開發語言,開發環境,目標平臺和版本等。這樣既能方便回答問題的熱心人及時有效地回復,又能方便以后的用戶來查找類似問題。盡量不要使用“跪求達人!!”,“我要瘋了!!”這類無意義的詞匯。
有關FAQ
基本所有技術社區,特別是微軟的社區,都會有提供FAQ之類的帖子或者公告,來幫助那些新手。先閱讀這些FAQ可以更快地認識現狀和了解常見的問題,也許你的問題就在里面。FAQ是新人必看的資料。
使用搜索
請在提問前充分使用你的搜索引擎,設想:如果你在某社區貼了一個問題,而當你回頭在網上搜索的時候,立即發現一模一樣的問題居然有數萬條結果,你會覺得剛剛那個問題有些愚蠢。而更尷尬的是,你發現搜索結果的第一條居然就是前不久另一個同行發在同樣的社區的,而且已經被解答了 = =!
心平氣和地提問
不需要使用“十萬火急!”,“在線急等!”這樣的字樣,每個人都很急切的希望知道問題的結果,但是不是每個人的問題都能及時被答復,這很正常。也不要在你的帖子中破口大罵,即便您出現的問題的確很可惡。更不要隨意攻擊某產品和它們的開發人員,因為這對解決你的問題沒有任何幫助,沒有人愿意和一個咆哮的吵架者探討問題。
明確問題
一個明確的問題才有可能得到合適的回答,在您提問之前,您或許應該先通過自己的嘗試盡量把您的問題明確化,具體化。否則很可能你在浪費彼此的時間。
貼代碼是一種美德
誰都知道貼相關代碼是表達問題最直觀的方式,但是這個也是最容易被忽略的方式。很多開發者總是習慣一吐為快的感覺,說了很多話但是都沒有說到點子上,對企圖幫助你的人來說,你貼代碼顯然要比碼字更容易讓他們復現問題。
別忘了開發和運行環境
你的程序是用什么開發的,VC6? VS2003/2005/2008?基于.Net Framework 2.0, 3.5?什么運行環境?Windows XP?Windows Vista?Windows Mobile 6 Professional?
注意對問題現象的描述
這對你的提問很關鍵,我經常看到有這樣的描述:“我試過了XXX函數,但是不行!”,請問不行是什么意思?有異常么?具體的錯誤信息是什么?還是根本沒有任何反應?
不要多版面
把同樣的問題放在多個版面沒有任何意義,反而不方便提問者去查詢有用信息。放到一個“合適”的版面即可。
不要求人代工
不論有償還是無償,在版面上求人代工都是不妥的,技術社區是專門用作交流技術而不是專門用作外包的,而且你也無權利轉嫁老板給你的任何任務給其他人。
及時關閉已解決主題
如果你的問題已經得到滿意的答案,請及時結貼,不要在同樣的主題貼下面引出多個問題,不斷提問。這樣既不利于你后面的問題被看到,也不利于其他用戶搜索問題。新的問題請開新的主題。
不要進行郵件騷擾
如非特別說明,不要通過郵件回復給社區用戶,這不是他們來社區所希望的,在社區回帖或者在博客留言都是很好的方式,別人在有空的時候自然會看到你的問題。這也是對他人的尊重,通常大家只是希望在自己有空的時候去社區看看問題,誰也不希望在上班時郵箱被一堆來自持有各種心態的提問者的問題給暴掉。
表示感謝
別忘了對給予你幫助的人表示感謝,盡管這個對你的問題是否得到解答沒有任何影響,但這是禮貌,是對別人勞動的尊重。如果您是屬于提問之后不管的類型,有可能會讓人產生對您RPWT的疑問,也許那些對你有過幫助的人就不再愿意回答你的問題了。
更多…
如果您和我一樣對提問的技巧感興趣,推薦您閱讀以下文章:
How To Ask Questions The Smart Way
http://en.wikipedia.org/wiki/Wikipedia:Help_desk/How_to_ask
摘自:http://www.cnblogs.com/fox23/archive/2008/08/27/how-to-ask-questions-the-smart-way.html
posted @
2008-10-28 15:10 Sandy 閱讀(152) |
評論 (0) |
編輯 收藏
模式對話框和非模式對話框的區別
一、 創建的區別
在WIN32中,模式對話框的創建一般是使用DialogBox來進行創建的。而非模式對話框則是利用CreateWindow來創建的。在MFC或是WTL中,模式對話框一般是使用DoModal,而非模式對話框的創建則是使用Create。
模式對話框創建后,程序的其他窗口便不能進行操作,必須將該窗口關閉后,其他窗口才能進行操作。而非模式對話框則無需這樣,它不強制要求用戶立即反應,而是與其他窗口同時接受用戶操作。
二、 消息響應的區別
在消息響應方面,模式對話框和非模式對話框之間又有著很大的區別。模式對話框工作的時候,它有內部的消息泵機制,控件之間的交互不用我們人為的去控制,系統會幫助我們去處理。非模式對話框則像普通窗口一樣,則由WinMain中書寫的消息循環驅動。但由于是對話框,它對一些消息有特殊的處理。因此,在消息循環中,需要先對對話框提供截獲消息的機會。
While (GetMessage(&msg, NULL, 0, 0))
{
if (hDlgModeless == 0 || !IsDialogMessage(hDlgModeless, &msg))
{
TranslateMessage(&msg);
DispatchMessage( &msg);
}
}
如果當前取得的消息是對話框的消息,IsDialogMessage 將它交由對話消息處理函數處理,并返回TRUE。不需要再派發了。
注意:這個方法并不是很好用,因為當對話框過多的時候,處理起來就比較麻煩了。另一種處理的方法是利用子類化控件的方法,來處理控件間的交互。
三、 銷毀的區別
模式對話框的銷毀是使用EndDialog,而非模式對話框的銷毀是使用DestroyWindow.。所以我們在銷毀對話框的時候,也要對其進行區別。
非模式對話框,用戶關閉對話框時,對話框消息處理函數將收到WM_CLOSE消息,接到后調用DestroyWindow以銷毀非模式對話框。
模式對話框,則一般響應IDOK和IDCANCEL。在PPC上,我們對于OK鍵和X鍵的處理要注意這點。
四、 其他
非模態對話框的模板必須具有Visible風格,否則對話框將不可見,而模態對話框則無需設置該項風格。更保險的辦法是調用ShowWindow(hDialog, SW_SHOW)來顯示對話框,而不管對話框是否具有Visible風格。
非模態對話框對象是用new操作符在堆中動態創建的,而不是以成員變量的形式嵌入到別的對象中或以局部變量的形式構建在堆棧上。通常應在對話框的擁有者窗口類內聲明一個指向對話框類的指針成員變量,通過該指針可訪問對話框對象。
通過調用Create函數來啟動對話框,而不是DoModal,這是模態對話框的關鍵所在。由于Create函數不會啟動新的消息循環,對話框與應用程序共用同一個消息循環,這樣對話框就不會壟斷用戶的輸入。Create在顯示了對話框后就立即返回,而DoModal是在對話框被關閉后才返回的。眾所周知,在MFC程序中,窗口對象的生存期應長于對應的窗口,也就是說,不能在未關閉屏幕上窗口的情況下先把對應的窗口對象刪除掉。由于在Create返回后,不能確定對話框是否已關閉,這樣也就無法確定對話框對象的生存期,因此只好在堆中構建對話框對象,而不能以局部變量的形式來構建之。
因為是用new操作符構建非模態對話框對象,因此必須在對話框關閉后,用delete操作符刪除對話框對象。
必須有一個標志表明非模態對話框是否是打開的。這樣做的原因是用戶有可能在打開一個模態對話框的情況下,又一次選擇打開命令。程序根據標志來決定是打開一個新的對話框,還是僅僅把原來打開的對話框激活。通常可以用擁有者窗口中的指向對話框對象的指針作為這種標志,當對話框關閉時,給該指針賦NULL值,以表明對話框對象已不存在了。
注意:在C++編程中,判斷一個位于堆中的對象是否存在的常用方法是判斷指向該對象的指針是否為空。這種機制要求程序員將指向該對象的指針初始化為NULL值,在創建對象時將返回的地址賦給該指針,而在刪除對象時將該指針置成NULL值。
posted @
2008-10-26 21:47 Sandy 閱讀(1384) |
評論 (1) |
編輯 收藏
最近看書,看到了引用,對其用法不是很了解。從各處匯總了一些知識,如下:
什么是引用
引用是某一變量(目標)的一個別名,對引用的操作與對變量直接操作完全一樣。引用的聲明方法:類型標識符 &引用名=目標變量名;
【例1】:
int a
int &ra=a; //定義引用ra, 它是變量a的引用,即別名
對引用的幾點說明
(1)&在此不是求地址運算,而是起標識作用。
(2)類型標識符是指目標變量的類型。
(3)聲明引用時,必須同時對其進行初始化。
(4)引用聲明完畢后,相當于目標變量名有兩個名稱,即該目標原名稱和引用名,且不能再把該引用名作為其他變量名的別名。
ra=1; 等價于 a=1;
(5)聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它本身不是一種數據類型,因此引用本身不占存儲單元,系統也不給引用分配存儲單元。故:對引用求地址,就是對目標變量求地址。&ra與&a相等
(6)不能建立數組的引用。因為數組是一個由若干個元素所組成的集合,所以無法建立一個數組的別名。
引用的用途
引用的主要用途是為了描述函數的參數和返回值,特別是為了運算符的重載。
1、 引用作為參數
引用的一個重要作用就是作為函數的參數。以前的C語言中函數參數傳遞是值傳遞,如果有大塊數據作為參數傳遞的時候,采用的方案往往是指針,因為這樣可以避免將整塊數據全部壓棧,可以提高程序的效率。但是現在(C++中)又增加了一種同樣有效率的選擇(在某些特殊情況下又是必須的選擇),就是引用。
【例2】:
void swap(int &p1, int &p2) / /此處函數的形參p1, p2都是引用
{ int p; p=p1; p1=p2; p2=p; }
為在程序中調用該函數,則相應的主調函數的調用點處,直接以變量作為實參進行調用即可,而不需要實參變量有任何的特殊要求。如:對應上面定義的swap函數,相應的主調函數可寫為:
【例3】:
main( )
{
int a,b;
cin>>a>>b; // 輸入a,b兩變量的值
swap(a,b); //直接以變量a和b作為實參調用swap函數
cout<<a<< ' ' <<b; //輸出結果
}
上述程序運行時,如果輸入數據10 20并回車后,則輸出結果為20 10。
由【例2】可看出:
(1)傳遞引用給函數與傳遞指針的效果是一樣的。這時,被調函數的形參就成為原來主調函數中的實參變量或對象的一個別名來使用,所以在被調函數中對形參變量的操作就是對其相應的目標對象(在主調函數中)的操作。
(2)使用引用傳遞函數的參數,在內存中并沒有產生實參的副本,它是直接對實參操作;而使用一般變量傳遞函數的參數,當發生函數調用時,需要給形參分配存儲單元,形參變量是實參變量的副本;如果傳遞的是對象,還將調用拷貝構造函數。因此,當參數傳遞的數據較大時,用引用比用一般變量傳遞參數的效率和所占空間都好。
(3)使用指針作為函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參分配存儲單元,且需要重復使用"*指針變量名"的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另一方面,在主調函數的調用點處,必須用變量的地址作為實參。而引用更容易使用,更清晰。
如果既要利用引用提高程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。
2、 引用作為返回值
如果一個函數返回了引用,那么該函數的調用也可以被賦值。這里有一函數,它擁有兩個引用參數并返回一個雙精度數的引用:
【例4】
double &max(double &d1,double &d2)
{
return d1>d2?d1:d2;
}
由于max()函數返回一個對雙精度數的引用,那么我們就可以用max() 來對其中較大的雙精度數加1:
max(x,y)+=1.0;
在Effecitve c++中指出,當你必須返回一個對象時不要試圖返回一個引用。這又是為什么呢?我看了一下它的解釋
考慮一個代表有理數的類,包含一個將兩個有理數相乘的函數:
class Rational {
public:
Rational(int numerator = 0, // see Item 24 for why this
int denominator = 1); // ctor isn't declared explicit
...
private:
int n, d; // numerator and denominator
friend const Rational // see Item 3 for why the
operator*(const Rational& lhs, // return type is cons
const Rational& rhs);
};
operator* 的這個版本以傳值方式返回它的結果,而且如果你沒有擔心那個對象的構造和析構的代價,你就是在推卸你的專業職責。如果你不是迫不得已,你不應該為這樣的一個對象付出成本。所以問題就在這里:你是迫不得已嗎?
哦,如果你能用返回一個引用來作為代替,你就不是迫不得已。但是,請記住一個引用僅僅是一個名字,一個實際存在的對象的名字。無論何時只要你看到一個引用的聲明,你應該立刻問自己它是什么東西的另一個名字,因為它必定是某物的另一個名字。在這個 operator* 的情況下,如果函數返回一個引用,它必須返回某個已存在的而且其中包含兩個對象相乘的產物的 Rational 對象的引用。
當然沒有什么理由期望這樣一個對象在調用 operator* 之前就存在。也就是說,如果你有
Rational a(1, 2); // a = 1/2
Rational b(3, 5); // b = 3/5
Rational c = a * b; // c should be 3/10
似乎沒有理由期望那里碰巧已經存在一個值為十分之三的有理數。不是這樣的,如果 operator* 返回這樣一個數的引用,它必須自己創建那個數字對象。
一個函數創建一個新對象僅有兩種方法:在棧上或者在堆上。棧上的生成物通過定義一個局部變量而生成。使用這個策略,你可以用這種方法試寫 operator*:
const Rational& operator*(const Rational& lhs, // warning! bad code!
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
你可以立即否決這種方法,因為你的目標是避免調用構造函數,而 result 正像任何其它對象一樣必須被構造。一個更嚴重的問題是這個函數返回一個引向 result 的引用,但是 result 是一個局部對象,而局部對象在函數退出時被銷毀。那么,這個 operator* 的版本不會返回引向一個 Rational 的引用——它返回引向一個前 Rational;一個曾經的 Rational;一個空洞的、惡臭的、腐敗的,從前是一個 Rational 但永不再是的尸體的引用,因為它已經被銷毀了。任何調用者甚至于沒有來得及匆匆看一眼這個函數的返回值就立刻進入了未定義行為的領地。這是事實,任何返回一個引向局部變量的引用的函數都是錯誤的。(對于任何返回一個指向局部變量的指針的函數同樣成立。)
那么,讓我們考慮一下在堆上構造一個對象并返回引向它的引用的可能性。基于堆的對象通過使用 new 而開始存在,所以你可以像這樣寫一個基于堆的 operator*:
const Rational& operator*(const Rational& lhs, // warning! more bad
const Rational& rhs) // code!
{
Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
哦,你還是必須要付出一個構造函數調用的成本,因為通過 new 分配的內存要通過調用一個適當的構造函數進行初始化,但是現在你有另一個問題:誰是刪除你用 new 做出來的對象的合適人選?
即使調用者盡職盡責且一心向善,它們也不太可能是用這樣的方案來合理地預防泄漏:
Rational w, x, y, z;
w = x * y * z;
這里,在同一個語句中有兩個 operator* 的調用,因此 new 被使用了兩次,這兩次都需要使用 delete 來銷毀。但是 operator* 的客戶沒有合理的辦法進行那些調用,因為他們沒有合理的辦法取得隱藏在通過調用 operator* 返回的引用后面的指針。這是一個早已注定的資源泄漏。
似乎明白了一些:當你必須返回一個對象時不要試圖返回一個引用。對于局部變量,返回其引用會造成錯誤。為了避免錯誤,我們應盡量不返回引用。
3、 常引用
const引用是指向const對象的引用:
const int ival = 1024;
const int &refVal = ival; // ok: both reference and object are const
int &ref2 = ival; // error: non const reference to a const object
常引用聲明方式:const 類型標識符 &引用名=目標變量名;
用這種方式聲明的引用,不能通過引用對目標變量的值進行修改,從而使引用的目標成為const,達到了引用的安全性。
【例5】:
int a ;
const int &ra = a;
ra=1; // 錯誤
a=1; // 正確
這不光是讓代碼更健壯,也有些其它方面的需要。
【例6】:假設有如下函數聲明:
string foo( );
void bar(string & s);
那么下面的表達式將是非法的:
bar(foo( ))
bar("hello world");
原因在于foo( )和"hello world"串都會產生一個臨時對象,而在C++中,這些臨時對象都是const類型的。因此上面的表達式就是試圖將一個const類型的對象轉換為非const類型,這是非法的。
引用型參數應該在能被定義為const的情況下,盡量定義為const 。
Const 引用與非const引用的區別
非const引用只能綁定到與該引用同類型的對象。
const引用則可以綁定到不同但相關的類型的對象或綁定到左值。
指針和引用的差別
1. 非空的差別任何情況下都不能使用指向空值的引用.一個引用必須總是指向某個對象. 不存在的指向空值的引用這個事實意味著使用引用的代碼效率比使用指針要高.
2. 合法性區別在使用引用之前不需要測試他的合法性.指針必須測試.
3. 可修改區別 指針可以被重新賦值給另一個不同的對象.但是引用總是指向在初始化的時候被制定的對象,以后不能改變.但是指定的對象其內容可以改變. 應該使用指針的情況: 可能存在不指向任何對象的可能性 需要在不同的時刻指向不同的對象(此時,你能夠改變指針的指向) 應該使用引用的情況: 如果總是指向一個對象并且一旦指向一個對象后就不會改變指向,使用此時應使用引用。
要首先好好理解指針和引用的區別
指針與引用看上去完全不同(指針用操作符’*’和’->’,引用使用操作符’.’),但是它們似乎有相同的功能。指針與引用都是讓你間接引用其他對象。你如何決定在什么時候使用指針,在什么時候使用引用呢?
總的來說,在以下情況下你應該使用指針,一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設置指針為空),二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象并且一旦指向一個對象后就不會改變指向,那么你應該使用引用。
還有一種情況,就是當你重載某個操作符時,你應該使用引用。最普通的例子是操作符[]。這個操作符典型的用法是返回一個目標對象,其能被賦值。
vector<int> v(10); //建立整形向量(vector),大小為10
//向量是一個在標準C庫中的一個模板 [Page]
v[5] = 10; // 這個被賦值的目標對象就是操作符[]返回的值
如果操作符[]返回一個指針,那么后一個語句就得這樣寫:
*v[5] = 10;
但是這樣會使得v看上去象是一個向量指針。因此你會選擇讓操作符返回一個引用
當你知道你必須指向一個對象并且不想改變其指向時,或者在重載操作符并為防止不必要的語義誤解時,你不應該使用指針。而在除此之外的其他情況下,則應使用指針 。
posted @
2008-10-12 21:18 Sandy 閱讀(254) |
評論 (0) |
編輯 收藏
今日從網上看到一篇好文章,匯總匯總,又拼湊拼湊,便有了下文。
static關鍵字是C、C++中都存在的關鍵字, 它主要有三種使用方式, 其中前兩種只指在C語言中使用, 第三種在C++中使用(C,C++中具體細微操作不盡相同, 本文以C++為準).
(1) 局部靜態變量 靜態局部變量有兩個用法,記憶功能和全局生存期.
(2) 外部靜態變量/函數 用于全局變量,主要作用是限制此全局變量被其他的文件調用
(3) 靜態數據成員/成員函數 表示這個成員是屬于這個類但是不屬于類中任意特定對象
下面就這三種使用方式及注意事項分別說明
一、局部靜態變量
在C/C++中, 局部變量按照存儲形式可分為三種auto, static, register
與auto類型(普通)局部變量相比, static局部變量有三點不同
1. 存儲空間分配不同
auto類型分配在棧上, 屬于動態存儲類別, 占動態存儲區空間, 函數調用結束后自動釋放, 而static分配在靜態存儲區, 在程序整個運行期間都不釋放. 兩者之間的作用域相同, 但生存期不同.
2. static局部變量在所處模塊在初次運行時進行初始化工作, 且只操作一次
3. 對于局部靜態變量, 如果不賦初值, 編譯期會自動賦初值0或空字符, 而auto類型的初值是不確定的.(對于C++中的class對象例外, class的對象實例如果不初始化, 則會自動調用默認構造函數, 不管是否是static類型)
特點: static局部變量的”記憶性”與生存期的”全局性”
所謂“記憶性”是指在兩次函數調用時, 在第二次調用進入時, 能保持第一次調用退出時的值.
示例程序一
#include <iostream>
using namespace std;
void staticLocalVar()
{
static int a = 0; // 運行期時初始化一次,下次再調用時,不進行初始化工作
cout<<"a="<<a<<endl;
++a;
}
int main()
{
staticLocalVar(); // 第一次調用, 輸出a=0
staticLocalVar(); // 第二次調用, 記憶了第一次退出時的值, 輸出a=1
return 0;
}
應用: 利用“記憶性”, 記錄函數調用的次數(示例程序一)
利用生存期的“全局性”,改善
“return a pointer / reference to a local object”的問題. Local object的問題在于退出函數, 生存期即結束,利用static的作用, 延長變量的生存期.
示例程序二:
// IP address to string format
// Used in Ethernet Frame and IP Header analysis
const char * IpToStr(UINT32 IpAddr)
{
static char strBuff[16]; // static局部變量, 用于返回地址有效
const unsigned char *pChIP = (const unsigned char *)&IpAddr;
sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
return strBuff;
}
注意事項:
1. “記憶性”, 程序運行很重要的一點就是可重復性, 而static變量的“記憶性”破壞了這種可重復性, 造成不同時刻至運行的結果可能不同.
2. “生存期”全局性和唯一性. 普通的local變量的存儲空間分配在stack上, 因此每次調用函數時, 分配的空間都可能不一樣, 而static具有全局唯一性的特點, 每次調用時, 都指向同一塊內存, 這就造成一個很重要的問題 ---- 不可重入性!!!
這樣在多線程程序設計或遞歸程序設計中, 要特別注意這個問題.
下面針對示例程序二, 分析在多線程情況下的不安全性.(為方便描述, 標上行號)
①const char * IpToStr(UINT32 IpAddr)
② {
③ static char strBuff[16]; // static局部變量, 用于返回地址有效
④ const unsigned char *pChIP = (const unsigned char *)&IpAddr;
⑤ sprintf(strBuff, "%u.%u.%u.%u", pChIP[0], pChIP[1], pChIP[2], pChIP[3]);
⑥ return strBuff;
⑦ }
假設現在有兩個線程A,B運行期間都需要調用IpToStr()函數, 將32位的IP地址轉換成點分10進制的字符串形式.
現A先獲得執行機會, 執行IpToStr(), 傳入的參數是0x0B090A0A, 順序執行完應該返回的指針存儲區內容是: “10.10.9.11”, 現執行到⑥時, 失去執行權, 調度到B線程執行, B線程傳入的參數是0xA8A8A8C0,執行至⑦, 靜態存儲區的內容是192.168.168.168. 當再調度到A執行時, 從⑥繼續執行, 由于strBuff的全局唯一性, 內容已經被B線程沖掉, 此時返回的將是192.168.168.168字符串, 不再是10.10.9.11字符串.
補充:靜態局部變量屬于靜態存儲方式,它具有以下特點:
(1)靜態局部變量在函數內定義 它的生存期為整個源程序,但是其作用域仍與自動變量相同,只能在定義該變量的函數內使用該變量。退出該函數后,盡管該變量還繼續存在,但不能使用它。
(2)允許對構造類靜態局部量賦初值 例如數組,若未賦以初值,則由系統自動賦以0值。
(3)對基本類型的靜態局部變量若在說明時未賦以初值,則系統自動賦予0值。而對自動變量不賦初值,則其值是不定的。根據靜態局部變量的特點, 可以看出它是一種生存期為整個源程序的量。雖然離開定義它的函數后不能使用,但如再次調用定義它的函數時,它又可繼續使用, 而且保存了前次被調用后留下的值。因此,當多次調用一個函數且要求在調用之間保留某些變量的值時,可考慮采用靜態局部變量。雖然用全局變量也可以達到上述目的,但全局變量有時會造成意外的副作用,因此仍以采用局部靜態變量為宜
二、外部靜態變量/函數
在C中static有了第二種含義:用來表示不能被其它文件訪問的全局變量和函數。 但為了限制全局變量/函數的作用域, 函數或變量前加static使得函數成為靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限于本文件(所以又稱內部函數)。注意此時, 對于外部(全局)變量, 不論是否有static限制, 它的存儲區域都是在靜態存儲區, 生存期都是全局的. 此時的static只是起作用域限制作用, 限定作用域在本模塊(文件)內部.
使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。
示例程序三:
//file1.cpp
static int varA;
int varB;
extern void funA()
{
……
}
static void funB()
{
……
}
//file2.cpp
extern int varB; // 使用file1.cpp中定義的全局變量
extern int varA; // 錯誤! varA是static類型, 無法在其他文件中使用
extern vod funA(); // 使用file1.cpp中定義的函數
extern void funB(); // 錯誤! 無法使用file1.cpp文件中static函數
補充:全局變量(外部變量)的說明之前再冠以static 就構成了靜態的全局變量。全局變量本身就是靜態存儲方式, 靜態全局變量當然也是靜態存儲方式。 這兩者在存儲方式上并無不同。這兩者的區別雖在于非靜態全局變量的作用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。 而靜態全局變量則限制了其作用域, 即只在定義該變量的源文件內有效, 在同一源程序的其它源文件中不能使用它。由于靜態全局變量的作用域局限于一個源文件內,只能為該源文件內的函數公用,因此可以避免在其它源文件中引起錯誤。從以上分析可以看出, 把局部變量改變為靜態變量后是改變了它的存儲方式即改變了它的生存期。把全局變量改變為靜態變量后是改變了它的作用域,限制了它的使用范圍。因此static 這個說明符在不同的地方所起的作用是不同的。
三、靜態數據成員/成員函數(C++特有)
C++重用了這個關鍵字,并賦予它與前面不同的第三種含義:表示屬于一個類而不是屬于此類的任何特定對象的變量和函數. 這是與普通成員函數的最大區別, 也是其應用所在, 比如在對某一個類的對象進行計數時,計數生成多少個類的實例, 就可以用到靜態數據成員. 在這里面, static既不是限定作用域的, 也不是擴展生存期的作用, 指示變量/函數在此類中的唯一性. 這也是“屬于一個類而不是屬于此類的任何特定對象的變量和函數”的含義. 因為它是對整個類來說是唯一的, 因此不可能屬于某一個實例對象的. (針對靜態數據成員而言, 成員函數不管是否是static,在內存中只有一個副本.普通成員函數調用時, 需要傳入this指針,static成員函數調用時, 沒有this指針. )
請看示例程序四
class EnemyTarget {
public:
EnemyTarget() { ++numTargets; }
EnemyTarget(const EnemyTarget&) { ++numTargets; }
~EnemyTarget() { --numTargets; }
static size_t numberOfTargets() { return numTargets; }
bool destroy(); // returns success of attempt to destroy
// EnemyTarget object
private:
static size_t numTargets; // object counter
};
// class statics must be defined outside the class;
// initialization is to 0 by default
size_t EnemyTarget::numTargets;
在這個例子中, 靜態數據成員numTargets就是用來計數產生的對象個數的.
在《c++ 程序設計語言》中,是這樣運用的:
Static靜態成員,它是類的一部分,但卻不是該類的各個對象的一部分。一個static成員只有唯一的一份副本,但不像常規的非static成員那樣在每個對象里各有一份副本。 與此類似,一個需要訪問類成員,然而卻并不需要針對特定對象去調用的函數,也被稱為一個static成員函數。其好處在于消除了由于依賴全局量而引起的問題
Class Date
{
Int d, m, y;
Static Date default_date;
Public:
Date(int dd=0, int mm=0, int yy=0);
//……
Static void set_default(int, int, int);
};
靜態成員可以像任何其他成員一樣引用,此外,對于靜態成員的引用不必提到任何對象,相反,在這里應該成員的名字加上作為限定詞的類的名字。
Void f()
{
Date::set_default(4, 5, 1945);
}
靜態成員(包括函數和數據成員)都必須在某個地方另行定義。如
Date Date:::default_date(16, 12, 1770);
Void Date::set_default(int d, int m, int y)
{
Date::default_date = Date(d, m, y);
}
補充:內部函數和外部函數
當一個源程序由多個源文件組成時,C語言根據函數能否被其它源文件中的函數調用,將函數分為內部函數和外部函數。
1 內部函數(又稱靜態函數)
如果在一個源文件中定義的函數,只能被本文件中的函數調用,而不能被同一程序其它文件中的函數調用,這種函數稱為內部函數。
定義一個內部函數,只需在函數類型前再加一個“static”關鍵字即可,如下所示:
static 函數類型 函數名(函數參數表)
{……}
關鍵字“static”,譯成中文就是“靜態的”,所以內部函數又稱靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限于本文件。
使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名,因為同名也沒有關系。
2 外部函數
外部函數的定義:在定義函數時,如果沒有加關鍵字“static”,或冠以關鍵字“extern”,表示此函數是外部函數:
[extern] 函數類型 函數名(函數參數表)
{……}
調用外部函數時,需要對其進行說明:
[extern] 函數類型 函數名(參數類型表)[,函數名2(參數類型表2)……];
posted @
2008-10-11 10:40 Sandy 閱讀(363) |
評論 (1) |
編輯 收藏
在windows mobile 上如何修改菜單上的文字呢?
我原先也只是看別人的代碼,然后copy過來自己用,有的時候弄不清楚所以然,就會出錯。
通過下面的方法,我們可以修改菜單上的文字:
1
HMENU hMenu=NULL;
2
TBBUTTONINFO tbbi =
{0};
3
tbbi.cbSize = sizeof(tbbi);
4
tbbi.dwMask = TBIF_LPARAM | TBIF_BYINDEX;
5
SendMessage(hMenuHWND, TB_GETBUTTONINFO, 1, (LPARAM)&tbbi); //修改菜單項 在左邊為0,在右邊為1
6
hMenu = (HMENU)tbbi.lParam;
7
8
InsertMenu(hMenu,beforeItem,MF_BYCOMMAND,afterItem,sText); //加入含有欲改寫文本的菜單項
9
DeleteMenu(hMenu,beforeItem,MF_BYCOMMAND); //刪除被改寫的菜單
通過上面的方法,我們就能修改菜單上的文字。
此外,學習一下TB_GETBUTTONINFO
消息:TB_GETBUTTONINFO
作用:This message retrieves the information for a button in a toolbar.
使用:
wParam = (WPARAM)(INT) iID; lParam = (LPARAM)(LPTBBUTTONINFO) lptbbi;
參數介紹:
iID Button identifier.
lptbbi Long pointer to a TBBUTTONINFO structure that receives the button information. The cbSize and dwMask members of this structure must be filled in prior to sending this message.
posted @
2008-07-30 18:41 Sandy 閱讀(904) |
評論 (0) |
編輯 收藏
如何獲得單個進程所占內存的大小,也許很簡單,通過GetProcessMemoryInfo可以輕松獲得,然而那是在PC上。但在windows mobile 上,這個函數不存在,它的實現機制我也不太清楚。所以如何獲得一個進程的占用內存大小,則需要另辟蹊徑。不過目前,我還沒有找到。
我現在的方法,如下:
1
DWORD GetUsedMemory(DWORD pID)
2

{
3
DWORD memUsage = 0;
4
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, pID);
5
if (INVALID_HANDLE_VALUE != hSnapShot)
6
{
7
HEAPLIST32 heapList;
8
heapList.dwSize = sizeof(HEAPLIST32);
9
BOOL bOk = Heap32ListFirst(hSnapShot, &heapList);
10
for (; bOk; bOk = Heap32ListNext(hSnapShot, &heapList))
11
{
12
HEAPENTRY32 he;
13
he.dwSize = sizeof(HEAPENTRY32);
14
BOOL fOK = Heap32First(hSnapShot, &he, pID, heapList.th32HeapID);
15
for(; fOK; fOK = Heap32Next(hSnapShot, &he))
16
{
17
memUsage += he.dwBlockSize;
18
}
19
}
20
21
22
23
// 關閉快照句柄
24
CloseToolhelp32Snapshot(hSnapShot);
25
}
26
27
return memUsage;
28
29
}
以上的這個方法與實際有出入。
有沒有更好的辦法呢?希望研究過的和正在研究的人,或者感興趣的人,能夠指點一二,讓我能夠走出困惑。
posted @
2008-07-29 16:21 Sandy 閱讀(1153) |
評論 (4) |
編輯 收藏
查了很多資料 ,都說對于多語言要這樣寫:
簡體中文:
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#endif //_WIN32
繁體中文
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHT)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
#pragma code_page(950)
#endif //_WIN32
英語
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
對于簡體中文和英語而言,似乎還能顯示正確,但對于繁體而言,代碼頁設置成950后,就會顯示出問題,很是疑惑。但只將代碼頁改回936,這個問題就消失了,想不通為什么?
希望能在知道的多一點。
posted @
2008-05-13 13:48 Sandy 閱讀(483) |
評論 (0) |
編輯 收藏