原文地址:http://topic.csdn.net/t/20050503/21/3982385.html
分析的很透徹!
LPCTSTR 與 GetBuffer(int nMinBufLength)
這兩個函數提供了與標準C的相容轉換。在實際中使用頻率很高,但卻是最容易出錯的地方。這兩個函數實際上返回的都是指標,但它們有何區別呢?以及調用它們後,幕後是做了怎樣的處理過程呢?
(1) LPCTSTR 它的執行過程其實很簡單,只是返回引用記憶體塊的串地址。 它是作為操作符重載提供的,
所以在代碼中有時可以隱式轉換,而有時卻需強制轉制。如:
CString str;
const char* p = (LPCTSTR)str;
//假設有這樣的一個函數,Test(const char* p); 你就可以這樣調用
Test(str);//這裡會隱式轉換為LPCTSTR
(2) GetBuffer(int nMinBufLength) 它類似,也會返回一個指標,不過它有點差別,返回的是LPTSTR
(3) 這兩者到底有何不同呢?我想告訴大家,其本質上完全不一樣,一般說LPCTSTR轉換後只應該當常量使用,或者做函數的入參;而GetBuffer(...)取出指標後,可以通過這個指標來修改裡面的內容,或者做函數的入參。為什麼呢?也許經常有這樣的代碼:
CString str("abcd");
char* p = (char*)(const char*)str;
p[2] = 'z';
其實,也許有這樣的代碼後,你的程式並沒有錯,而且程式也運行得挺好。但它卻是非常危險的。再看
CString str("abcd");
CString test = str;
....
char* p = (char*)(const char*)str;
p[2] = 'z';
strcpy(p, "akfjaksjfakfakfakj");//這下完蛋了
你知道此時,test中的值是多少嗎?答案是"abzd".它也跟著改變了,這不是你所期望發生的。但為什麼會這樣呢?你稍微想想就會明白,前面說過,因為CString是指向引用塊的,str與test指向同一塊地方,當你p[2]='z'後,當然test也會隨著改變。所以用它做LPCTSTR做轉換後,你只能去讀這塊資料,千萬別去改變它的內容。
假如我想直接通過指標去修改資料的話,那怎樣辦呢?就是用GetBuffer(...).看下述代碼:
CString str("abcd");
CString test = str;
....
char* p = str.GetBuffer(20);
p[2] = 'z'; // 執行到此,現在test中值卻仍是"abcd"
strcpy(p, "akfjaksjfakfakfakj"); // 執行到此,現在test中值還是"abcd"
為什麼會這樣?其實GetBuffer(20)調用時,它實際上另外建立了一塊新內塊存,並分配20位元組長度的buffer,而原來的記憶體區塊引述計數也相應減1. 所以執行代碼後str與test是指向了兩塊不同的地方,所以相安無事。
(4) 不過這裡還有一點注意事項:就是str.GetBuffer(20)後,str的分配長度為20,即指標p它所指向的buffer只有20位元組長,給它賦值時,切不可超過,否則災難離你不遠了;如果指定長度小於原來串長度,如GetBuffer(1),實際上它會分配4個位元組長度(即原來串長度);另外,當調用GetBuffer(...)後並改變其內容,一定要記得調用ReleaseBuffer(),這個函數會根據串內容來更新引用記憶體塊的頭部資訊。
(5) 最後還有一注意事項,看下述代碼:
char* p = NULL;
const char* q = NULL;
{
CString str = "abcd";
q = (LPCTSTR)str;
p = str.GetBuffer(20);
AfxMessageBox(q);// 合法的
strcpy(p, "this is test");//合法的,
}
AfxMessageBox(q);// 非法的,可能完蛋
strcpy(p, "this is test");//非法的,可能完蛋
這裡要說的就是,當返回這些指標後, 如果CString對象生命結束,這些指標也相應無效。
3 拷貝 & 賦值 & "引用記憶體塊" 什麼時候釋放?
下面示範一段代碼執行過程
void Test()
{
CString str("abcd");//str指向一引用記憶體塊(引用記憶體塊的引用計數為1,
長度為4,分配長度為4)
CString a;//a指向一初始資料狀態,
a = str; //a與str指向同一引用記憶體塊(引用記憶體塊的引用計數為2,
長度為4,分配長度為4)
CString b(a);//a、b與str指向同一引用記憶體塊(引用記憶體塊的引用
計數為3,長度為4,分配長度為4)
{
LPCTSTR temp = (LPCTSTR)a;//temp指向引用記憶體塊的串首地址。
(引用記憶體塊的引用計數為3,長度為4,分配長度為4)
CString d = a; //a、b、d與str指向同一引用記憶體塊(引用記憶體塊的引用計數為4, 長度為4,分配長度為4)
b = "testa"; //這條語句實際是調用CString::operator=(CString&)函數。
b指向一新分配的引用記憶體塊。(新分配的引用記憶體塊的
引用計數為1,長度為5,分配長度為5)
//同時原引用記憶體區塊引述計數減1. a、d與str仍指向原
引用記憶體塊(引用記憶體塊的引用計數為3,長度為4,分配長度為4)
}//由於d生命結束,調用解構函式,導至引用計數減1(引用記憶體
塊的引用計數為2,長度為4,分配長度為4)
LPTSTR temp = a.GetBuffer(10);//此語句也會導致重新分配新記憶體塊。
temp指向新分配引用記憶體塊的串首地址(新
分配的引用記憶體塊的引用計數為1,長度
為0,分配長度為10)
//同時原引用記憶體區塊引述計數減1. 只有str仍
指向原引用記憶體塊(引用記憶體塊的引用計數為1,
長度為4,分配長度為4)
strcpy(temp, "temp"); //a指向的引用記憶體塊的引用計數為1,長度為0,分配長度為10
a.ReleaseBuffer();//注意:a指向的引用記憶體塊的引用計數為1,長度為4,分配長度為10
}
//執行到此,所有的局部變數生命週期都已結束。對象str a b 各自調用自己的析構構
//函數,所指向的引用記憶體塊也相應減1
//注意,str a b 所分別指向的引用記憶體塊的計數均為0,這導致所分配的記憶體塊釋放
通過觀察上面執行過程,我們會發現CString雖然可以多個對象指向同一引用內塊存,但是它們在進行各種拷貝、賦值及改變串內容時,它的處理是很智能並且非常安全的,完全做到了互不干涉、互不影響。當然必須要求你的代碼使用正確恰當,特別是實際使用中會有更複雜的情況,如做函數參數、引用、及有時需儲存到CStringList當中,如果哪怕有一小塊地方使用不當,其結果也會導致發生不可預知的錯誤