簡(jiǎn)介
對(duì)于很多初學(xué)者來(lái)說(shuō),往往覺(jué)得回調(diào)函數(shù)很神秘,很想知道回調(diào)函數(shù)的工作原理。本文將要解釋什么是回調(diào)函數(shù)、它們有什么好處、為什么要使用它們等等問(wèn)題,在開(kāi)始之前,假設(shè)你已經(jīng)熟知了函數(shù)指針。
什么是回調(diào)函數(shù)?
簡(jiǎn)而言之,回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個(gè)函數(shù),當(dāng)這個(gè)指針被用為調(diào)用它所指向的函數(shù)時(shí),我們就說(shuō)這是回調(diào)函數(shù)。
為什么要使用回調(diào)函數(shù)?
因?yàn)榭梢园颜{(diào)用者與被調(diào)用者分開(kāi)。調(diào)用者不關(guān)心誰(shuí)是被調(diào)用者,所有它需知道的,只是存在一個(gè)具有某種特定原型、某些限制條件(如返回值為int)的被調(diào)用函數(shù)。
如果想知道回調(diào)函數(shù)在實(shí)際中有什么作用,先假設(shè)有這樣一種情況,我們要編寫(xiě)一個(gè)庫(kù),它提供了某些排序算法的實(shí)現(xiàn),如冒泡排序、快速排序、shell排序、shake排序等等,但為使庫(kù)更加通用,不想在函數(shù)中嵌入排序邏輯,而讓使用者來(lái)實(shí)現(xiàn)相應(yīng)的邏輯;或者,想讓庫(kù)可用于多種數(shù)據(jù)類(lèi)型(int、float、string),此時(shí),該怎么辦呢?可以使用函數(shù)指針,并進(jìn)行回調(diào)。
回調(diào)可用于通知機(jī)制,例如,有時(shí)要在程序中設(shè)置一個(gè)計(jì)時(shí)器,每到一定時(shí)間,程序會(huì)得到相應(yīng)的通知,但通知機(jī)制的實(shí)現(xiàn)者對(duì)我們的程序一無(wú)所知。而此時(shí),就需有一個(gè)特定原型的函數(shù)指針,用這個(gè)指針來(lái)進(jìn)行回調(diào),來(lái)通知我們的程序事件已經(jīng)發(fā)生。實(shí)際上,SetTimer() API使用了一個(gè)回調(diào)函數(shù)來(lái)通知計(jì)時(shí)器,而且,萬(wàn)一沒(méi)有提供回調(diào)函數(shù),它還會(huì)把一個(gè)消息發(fā)往程序的消息隊(duì)列。
另一個(gè)使用回調(diào)機(jī)制的API函數(shù)是EnumWindow(),它枚舉屏幕上所有的頂層窗口,為每個(gè)窗口調(diào)用一個(gè)程序提供的函數(shù),并傳遞窗口的處理程序。如果被調(diào)用者返回一個(gè)值,就繼續(xù)進(jìn)行迭代,否則,退出。EnumWindow()并不關(guān)心被調(diào)用者在何處,也不關(guān)心被調(diào)用者用它傳遞的處理程序做了什么,它只關(guān)心返回值,因?yàn)榛诜祷刂担鼘⒗^續(xù)執(zhí)行或退出。
不管怎么說(shuō),回調(diào)函數(shù)是繼續(xù)自C語(yǔ)言的,因而,在C++中,應(yīng)只在與C代碼建立接口,或與已有的回調(diào)接口打交道時(shí),才使用回調(diào)函數(shù)。除了上述情況,在C++中應(yīng)使用虛擬方法或函數(shù)符(functor),而不是回調(diào)函數(shù)。
一個(gè)簡(jiǎn)單的回調(diào)函數(shù)實(shí)現(xiàn)
下面創(chuàng)建了一個(gè)sort.dll的動(dòng)態(tài)鏈接庫(kù),它導(dǎo)出了一個(gè)名為CompareFunction的類(lèi)型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回調(diào)函數(shù)的類(lèi)型。另外,它也導(dǎo)出了兩個(gè)方法:Bubblesort()和Quicksort(),這兩個(gè)方法原型相同,但實(shí)現(xiàn)了不同的排序算法。
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);
void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc); |
這兩個(gè)函數(shù)接受以下參數(shù):
·byte * array:指向元素?cái)?shù)組的指針(任意類(lèi)型)。
·int size:數(shù)組中元素的個(gè)數(shù)。
·int elem_size:數(shù)組中一個(gè)元素的大小,以字節(jié)為單位。
·CompareFunction cmpFunc:帶有上述原型的指向回調(diào)函數(shù)的指針。
這兩個(gè)函數(shù)的會(huì)對(duì)數(shù)組進(jìn)行某種排序,但每次都需決定兩個(gè)元素哪個(gè)排在前面,而函數(shù)中有一個(gè)回調(diào)函數(shù),其地址是作為一個(gè)參數(shù)傳遞進(jìn)來(lái)的。對(duì)編寫(xiě)者來(lái)說(shuō),不必介意函數(shù)在何處實(shí)現(xiàn),或它怎樣被實(shí)現(xiàn)的,所需在意的只是兩個(gè)用于比較的元素的地址,并返回以下的某個(gè)值(庫(kù)的編寫(xiě)者和使用者都必須遵守這個(gè)約定):
·-1:如果第一個(gè)元素較小,那它在已排序好的數(shù)組中,應(yīng)該排在第二個(gè)元素前面。
·0:如果兩個(gè)元素相等,那么它們的相對(duì)位置并不重要,在已排序好的數(shù)組中,誰(shuí)在前面都無(wú)所謂。
·1:如果第一個(gè)元素較大,那在已排序好的數(shù)組中,它應(yīng)該排第二個(gè)元素后面。
基于以上約定,函數(shù)Bubblesort()的實(shí)現(xiàn)如下,Quicksort()就稍微復(fù)雜一點(diǎn):
void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc) { for(int i=0; i < size; i++) { for(int j=0; j < size-1; j++) { //回調(diào)比較函數(shù) if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size)) { //兩個(gè)相比較的元素相交換 byte* temp = new byte[elem_size]; memcpy(temp, array+j*elem_size, elem_size); memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size); memcpy(array+(j+1)*elem_size, temp, elem_size); delete [] temp; } } } } |
注意:因?yàn)閷?shí)現(xiàn)中使用了memcpy(),所以函數(shù)在使用的數(shù)據(jù)類(lèi)型方面,會(huì)有所局限。
對(duì)使用者來(lái)說(shuō),必須有一個(gè)回調(diào)函數(shù),其地址要傳遞給Bubblesort()函數(shù)。下面有二個(gè)簡(jiǎn)單的示例,一個(gè)比較兩個(gè)整數(shù),而另一個(gè)比較兩個(gè)字符串:
int __stdcall CompareInts(const byte* velem1, const byte* velem2) { int elem1 = *(int*)velem1; int elem2 = *(int*)velem2;
if(elem1 < elem2) return -1; if(elem1 > elem2) return 1;
return 0; }
int __stdcall CompareStrings(const byte* velem1, const byte* velem2) { const char* elem1 = (char*)velem1; const char* elem2 = (char*)velem2; return strcmp(elem1, elem2); } |
下面另有一個(gè)程序,用于測(cè)試以上所有的代碼,它傳遞了一個(gè)有5個(gè)元素的數(shù)組給Bubblesort()和Quicksort(),同時(shí)還傳遞了一個(gè)指向回調(diào)函數(shù)的指針。
int main(int argc, char* argv[]) { int i; int array[] = {5432, 4321, 3210, 2109, 1098};
cout << "Before sorting ints with Bubblesort\n"; for(i=0; i < 5; i++) cout << array[i] << '\n';
Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);
cout << "After the sorting\n"; for(i=0; i < 5; i++) cout << array[i] << '\n';
const char str[5][10] = {"estella","danielle","crissy","bo","angie"};
cout << "Before sorting strings with Quicksort\n"; for(i=0; i < 5; i++) cout << str[i] << '\n';
Quicksort((byte*)str, 5, 10, &CompareStrings);
cout << "After the sorting\n"; for(i=0; i < 5; i++) cout << str[i] << '\n';
return 0; } |
如果想進(jìn)行降序排序(大元素在先),就只需修改回調(diào)函數(shù)的代碼,或使用另一個(gè)回調(diào)函數(shù),這樣編程起來(lái)靈活性就比較大了。
調(diào)用約定
上面的代碼中,可在函數(shù)原型中找到__stdcall,因?yàn)樗噪p下劃線打頭,所以它是一個(gè)特定于編譯器的擴(kuò)展,說(shuō)到底也就是微軟的實(shí)現(xiàn)。任何支持開(kāi)發(fā)基于Win32的程序都必須支持這個(gè)擴(kuò)展或其等價(jià)物。以__stdcall標(biāo)識(shí)的函數(shù)使用了標(biāo)準(zhǔn)調(diào)用約定,為什么叫標(biāo)準(zhǔn)約定呢,因?yàn)樗械腤in32 API(除了個(gè)別接受可變參數(shù)的除外)都使用它。標(biāo)準(zhǔn)調(diào)用約定的函數(shù)在它們返回到調(diào)用者之前,都會(huì)從堆棧中移除掉參數(shù),這也是Pascal的標(biāo)準(zhǔn)約定。但在C/C++中,調(diào)用約定是調(diào)用者負(fù)責(zé)清理堆棧,而不是被調(diào)用函數(shù);為強(qiáng)制函數(shù)使用C/C++調(diào)用約定,可使用__cdecl。另外,可變參數(shù)函數(shù)也使用C/C++調(diào)用約定。
Windows操作系統(tǒng)采用了標(biāo)準(zhǔn)調(diào)用約定(Pascal約定),因?yàn)槠淇蓽p小代碼的體積。這點(diǎn)對(duì)早期的Windows來(lái)說(shuō)非常重要,因?yàn)槟菚r(shí)它運(yùn)行在只有640KB內(nèi)存的電腦上。
如果你不喜歡__stdcall,還可以使用CALLBACK宏,它定義在windef.h中:
#define CALLBACK __stdcallor
#define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall |
作為回調(diào)函數(shù)的C++方法 因?yàn)槠綍r(shí)很可能會(huì)使用到C++編寫(xiě)代碼,也許會(huì)想到把回調(diào)函數(shù)寫(xiě)成類(lèi)中的一個(gè)方法,但先來(lái)看看以下的代碼:
class CCallbackTester { public: int CALLBACK CompareInts(const byte* velem1, const byte* velem2); };
Bubblesort((byte*)array, 5, sizeof(array[0]), &CCallbackTester::CompareInts); |
如果使用微軟的編譯器,將會(huì)得到下面這個(gè)編譯錯(cuò)誤:
error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible |
這是因?yàn)榉庆o態(tài)成員函數(shù)有一個(gè)額外的參數(shù):this指針,這將迫使你在成員函數(shù)前面加上static。當(dāng)然,還有幾種方法可以解決這個(gè)問(wèn)題,但限于篇幅,就不再論述了。
WM_DESTROY 和 WM_NCDESTROY 消息之間有什么區(qū)別?
原文鏈接
What is the difference between WM_DESTROY and WM_NCDESTROY? 在窗口銷(xiāo)毀時(shí)有兩個(gè)緊密關(guān)聯(lián)的 windows 消息, 就是 WM_DESTROY 和 WM_NCDESTROY. 它們有何區(qū)別?
區(qū)別就是 WM_DESTROY 消息是在窗口銷(xiāo)毀動(dòng)作序列中的開(kāi)始被發(fā)送的, 而 WM_NCDESTROY 消息是在結(jié)尾. 這在你的窗口擁有子窗口時(shí)是個(gè)重大區(qū)別. 如果你有一個(gè)帶子窗口的父窗口, 那么消息的發(fā)送序列 (在沒(méi)有怪誕行為影響的前提下) 就像這樣:
hwnd = parent, uMsg = WM_DESTROY
hwnd = child, uMsg = WM_DESTROY
hwnd = child, uMsg = WM_NCDESTROY
hwnd = parent, uMsg = WM_NCDESTROY
注意, 父窗口是在子窗口被銷(xiāo)毀之前收到 WM_DESTROY 消息, 在子窗口被銷(xiāo)毀之后收到 WM_NCDESTROY 消息.
兩個(gè)銷(xiāo)毀消息, 一個(gè)在開(kāi)頭, 一個(gè)在結(jié)尾, 這意味著, 對(duì)于你自己的模塊, 你可以通過(guò)處理相應(yīng)的消息來(lái)執(zhí)行清理操作.
例如, 如果有些東西必須在開(kāi)頭清理, 那么你可以使用 WM_DESTROY 消息.
WM_NCDESTROY 消息是你窗口將會(huì)收到的最后一個(gè)消息 (在沒(méi)有怪誕行為影響的前提下), 因此, 這里是做 "最終清理" 的最佳場(chǎng)所.
這就是為什么我們的
new scratch 程序會(huì)一直等到 WM_NCDESTROY 銷(xiāo)毀它的實(shí)例變量, 才會(huì)返回.
與這兩個(gè)銷(xiāo)毀消息配對(duì)的, 是 WM_CREATE 和 WM_NCCREATE 這兩個(gè)類(lèi)似的消息. 與 WM_NCDESTROY 是你窗口收到的最后一條消息類(lèi)似,
WM_NCCREATE 消息是第一條消息, 這是一個(gè)創(chuàng)建你自己的實(shí)例變量的好地方. 需要注意的是, 如果你導(dǎo)致 WM_NCCREATE 消息返回失敗,
那么所有你將收到的消息就只有 WM_NCDESTROY 了; 不會(huì)有 WM_DESTROY 消息了, 因?yàn)槟愀揪蜎](méi)有收到相應(yīng)的 WM_CREATE 消息.
那么什么是我一直在暗示的 "怪誕行為" 呢? 下一次 (
When the normal window destruction messages are thrown for a loop) 我們?cè)僬f(shuō)
1. CString實(shí)現(xiàn)的機(jī)制.
CString是通過(guò)“引用”來(lái)管理串的,“引用”這個(gè)詞我相信大家并不陌生,象Window內(nèi)核對(duì)象、COM對(duì)象等都是通過(guò)引用來(lái)實(shí)現(xiàn)的。而CString也是通過(guò)這樣的機(jī)制來(lái)管理分配的內(nèi)存塊。實(shí)際上CString對(duì)象只有一個(gè)指針成員變量,所以任何CString實(shí)例的長(zhǎng)度只有4字節(jié).
即: int len = sizeof(CString);//len等于4
這個(gè)指針指向一個(gè)相關(guān)的引用內(nèi)存塊,如圖: CString str("abcd");
‘A’
‘B’
‘C’
‘D’
0
0x04040404 head部,為引用內(nèi)存塊相關(guān)信息
str 0x40404040
正因?yàn)槿绱?,一個(gè)這樣的內(nèi)存塊可被多個(gè)CString所引用,例如下列代碼:
CString str("abcd");
CString a = str;
CString b(str);
CString c;
c = b;
上面代碼的結(jié)果是:上面四個(gè)對(duì)象(str,a,b,c)中的成員變量指針有相同的值,都為0x40404040.而這塊內(nèi)存塊怎么知道有多少個(gè)CString引用它呢?同樣,它也會(huì)記錄一些信息。如被引用數(shù),串長(zhǎng)度,分配內(nèi)存長(zhǎng)度。
這塊引用內(nèi)存塊的結(jié)構(gòu)定義如下:
struct CStringData
{
long nRefs; //表示有多少個(gè)CString 引用它. 4
int nDataLength; //串實(shí)際長(zhǎng)度. 4
int nAllocLength; //總共分配的內(nèi)存長(zhǎng)度(不計(jì)這頭部的12字節(jié)). 4
};
由于有了這些信息,CString就能正確地分配、管理、釋放引用內(nèi)存塊。
如果你想在調(diào)試程序的時(shí)候獲得這些信息。可以在Watch窗口鍵入下列表達(dá)式:
(CStringData*)((CStringData*)(this->m_pchData)-1)或
(CStringData*)((CStringData*)(str.m_pchData)-1)//str為指CString實(shí)例
正因?yàn)椴捎昧诉@樣的好機(jī)制,使得CString在大量拷貝時(shí),不僅效率高,而且分配內(nèi)存少。
2.LPCTSTR 與 GetBuffer(int nMinBufLength)
這兩個(gè)函數(shù)提供了與標(biāo)準(zhǔn)C的兼容轉(zhuǎn)換。在實(shí)際中使用頻率很高,但卻是最容易出錯(cuò)的地方。這兩個(gè)函數(shù)實(shí)際上返回的都是指針,但它們有何區(qū)別呢?以及調(diào)用它們后,幕后是做了怎樣的處理過(guò)程呢?
(1) LPCTSTR 它的執(zhí)行過(guò)程其實(shí)很簡(jiǎn)單,只是返回引用內(nèi)存塊的串地址。 它是作為操作符重載提供的,所以在代碼中有時(shí)可以隱式轉(zhuǎn)換,而有時(shí)卻需強(qiáng)制轉(zhuǎn)制。如:
CString str;
const char* p = (LPCTSTR)str;
//假設(shè)有這樣的一個(gè)函數(shù),Test(const char* p); 你就可以這樣調(diào)用
Test(str);//這里會(huì)隱式轉(zhuǎn)換為L(zhǎng)PCTSTR
(2) GetBuffer(int nMinBufLength) 它類(lèi)似,也會(huì)返回一個(gè)指針,不過(guò)它有點(diǎn)差別,返回的是LPTSTR
(3) 這兩者到底有何不同呢?我想告訴大家,其本質(zhì)上完全不一樣,一般說(shuō)LPCTSTR轉(zhuǎn)換后只應(yīng)該當(dāng)常量使用,或者做函數(shù)的入?yún)?;而GetBuffer(...)取出指針后,可以通過(guò)這個(gè)指針來(lái)修改里面的內(nèi)容,或者做函數(shù)的出參。為什么呢?也許經(jīng)常有這樣的代碼:
CString str("abcd");
char* p = (char*)(const char*)str;
p[2] = 'z';
其實(shí),也許有這樣的代碼后,你的程序并沒(méi)有錯(cuò),而且程序也運(yùn)行得挺好。但它卻是非常危險(xiǎn)的。再看
CString str("abcd");
CString test = str;
....
char* p = (char*)(const char*)str;
p[2] = 'z';
strcpy(p, "akfjaksjfakfakfakj");//這下完蛋了
你知道此時(shí),test中的值是多少嗎?答案是"abzd"。它也跟著改變了,這不是你所期望發(fā)生的。但為什么會(huì)這樣呢?你稍微想想就會(huì)明白,前面說(shuō)過(guò),因?yàn)镃String是指向引用塊的,str與test指向同一塊地方,當(dāng)你p[2]='z'后,當(dāng)然test也會(huì)隨著改變。所以用它做LPCTSTR做轉(zhuǎn)換后,你只能去讀這塊數(shù)據(jù),千萬(wàn)別去改變它的內(nèi)容。
假如我想直接通過(guò)指針去修改數(shù)據(jù)的話,那怎樣辦呢?就是用GetBuffer(...).看下述代碼:
CString str("abcd");
CString test = str;
....
char* p = str.GetBuffer(20);
p[2] = 'z'; // 執(zhí)行到此,現(xiàn)在test中值卻仍是"abcd"
strcpy(p, "akfjaksjfakfakfakj"); // 執(zhí)行到此,現(xiàn)在test中值還是"abcd"
為什么會(huì)這樣?其實(shí)GetBuffer(20)調(diào)用時(shí),它實(shí)際上另外建立了一塊新內(nèi)塊存,并分配20字節(jié)長(zhǎng)度的buffer,而原來(lái)的內(nèi)存塊引用計(jì)數(shù)也相應(yīng)減1. 所以執(zhí)行代碼后str與test是指向了兩塊不同的地方,所以相安無(wú)事。
(4) 不過(guò)這里還有一點(diǎn)注意事項(xiàng):就是str.GetBuffer(20)后,str的分配長(zhǎng)度為20,即指針p它所指向的buffer只有20字節(jié)長(zhǎng),給它賦值時(shí),切不可超過(guò),否則災(zāi)難離你不遠(yuǎn)了;如果指定長(zhǎng)度小于原來(lái)串長(zhǎng)度,如GetBuffer(1),實(shí)際上它會(huì)分配4個(gè)字節(jié)長(zhǎng)度(即原來(lái)串長(zhǎng)度);另外,當(dāng)調(diào)用GetBuffer(...)后并改變其內(nèi)容,一定要記得調(diào)用ReleaseBuffer(),這個(gè)函數(shù)會(huì)根據(jù)串內(nèi)容來(lái)更新引用內(nèi)存塊的頭部信息。
(5) 最后還有一注意事項(xiàng),看下述代碼:
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");//非法的,可能完蛋
這里要說(shuō)的就是,當(dāng)返回這些指針后, 如果CString對(duì)象生命結(jié)束,這些指針也相應(yīng)無(wú)效。
3.拷貝 & 賦值 & "引用內(nèi)存塊" 什么時(shí)候釋放?
下面演示一段代碼執(zhí)行過(guò)程
void Test()
{
CString str("abcd");
//str指向一引用內(nèi)存塊(引用內(nèi)存塊的引用計(jì)數(shù)為1,長(zhǎng)度為4,分配長(zhǎng)度為4)
CString a;
//a指向一初始數(shù)據(jù)狀態(tài),
a = str;
//a與str指向同一引用內(nèi)存塊(引用內(nèi)存塊的引用計(jì)數(shù)為2,長(zhǎng)度為4,分配長(zhǎng)度為4)
CString b(a);
//a、b與str指向同一引用內(nèi)存塊(引用內(nèi)存塊的引用計(jì)數(shù)為3,長(zhǎng)度為4,分配長(zhǎng)度為4)
{
LPCTSTR temp = (LPCTSTR)a;
//temp指向引用內(nèi)存塊的串首地址。(引用內(nèi)存塊的引用計(jì)數(shù)為3,長(zhǎng)度為4,分配長(zhǎng)度為4)
CString d = a;
//a、b、d與str指向同一引用內(nèi)存塊(引用內(nèi)存塊的引用計(jì)數(shù)為4, 長(zhǎng)度為4,分配長(zhǎng)度為4)
b = "testa";
//這條語(yǔ)句實(shí)際是調(diào)用CString::operator=(CString&)函數(shù)。 b指向一新分配的引用內(nèi)存塊。(新分配的引用內(nèi)存塊的 引用計(jì)數(shù)為1, 長(zhǎng)度為5, 分配長(zhǎng)度為5)
//同時(shí)原引用內(nèi)存塊引用計(jì)數(shù)減1. a、d與str仍指向原 引用內(nèi)存塊(引用內(nèi)存塊的引用計(jì)數(shù)為3,長(zhǎng)度為4,分配長(zhǎng)度為4)
}
//由于d生命結(jié)束,調(diào)用析構(gòu)函數(shù),導(dǎo)至引用計(jì)數(shù)減1(引用內(nèi)存塊的引用計(jì)數(shù)為2,長(zhǎng)度為4,分配長(zhǎng)度為4)
LPTSTR temp = a.GetBuffer(10);
//此語(yǔ)句也會(huì)導(dǎo)致重新分配新內(nèi)存塊。temp指向新分配引用內(nèi)存塊的串首地址(新 分配的引用內(nèi)存塊的引用計(jì)數(shù)為1,長(zhǎng)度為0,分配長(zhǎng)度為10)
//同時(shí)原引用內(nèi)存塊引用計(jì)數(shù)減1. 只有str仍 指向原引用內(nèi)存塊 (引用內(nèi)存塊的引用計(jì)數(shù)為1, 長(zhǎng)度為4, 分配長(zhǎng)度為4)
strcpy(temp, "temp");
//a指向的引用內(nèi)存塊的引用計(jì)數(shù)為1,長(zhǎng)度為0,分配長(zhǎng)度為10 a.ReleaseBuffer();//注意:a指向的引用內(nèi)存塊的引用計(jì)數(shù)為1,長(zhǎng)度為4,分配長(zhǎng)度為10
}
//執(zhí)行到此,所有的局部變量生命周期都已結(jié)束。對(duì)象str a b 各自調(diào)用自己的析構(gòu)構(gòu)
//函數(shù),所指向的引用內(nèi)存塊也相應(yīng)減1
//注意,str a b 所分別指向的引用內(nèi)存塊的計(jì)數(shù)均為0,這導(dǎo)致所分配的內(nèi)存塊釋放
通過(guò)觀察上面執(zhí)行過(guò)程,我們會(huì)發(fā)現(xiàn)CString雖然可以多個(gè)對(duì)象指向同一引用內(nèi)塊存,但是它們?cè)谶M(jìn)行各種拷貝、賦值及改變串內(nèi)容時(shí),它的處理是很智能并且非常安全的,完全做到了互不干涉、互不影響。當(dāng)然必須要求你的代碼使用正確恰當(dāng),特別是實(shí)際使用中會(huì)有更復(fù)雜的情況,如做函數(shù)參數(shù)、引用、及有時(shí)需保存到CStringList當(dāng)中,如果哪怕有一小塊地方使用不當(dāng),其結(jié)果也會(huì)導(dǎo)致發(fā)生不可預(yù)知的錯(cuò)誤
5 FreeExtra()的作用
看這段代碼
(1) CString str("test");
(2) LPTSTR temp = str.GetBuffer(50);
(3) strcpy(temp, "there are 22 character");
(4) str.ReleaseBuffer();
(5) str.FreeExtra();
上面代碼執(zhí)行到第(4)行時(shí),大家都知道str指向的引用內(nèi)存塊計(jì)數(shù)為1,長(zhǎng)度為22,分配長(zhǎng)度為50. 那么執(zhí)行str.FreeExtra()時(shí),它會(huì)釋放所分配的多余的內(nèi)存。(引用內(nèi)存塊計(jì)數(shù)為1,長(zhǎng)度為22,分配長(zhǎng)度為22)
6 Format(...) 與 FormatV(...)
這條語(yǔ)句在使用中是最容易出錯(cuò)的。因?yàn)樗罡挥屑记尚?,也相?dāng)靈活。在這里,我沒(méi)打算對(duì)它細(xì)細(xì)分析,實(shí)際上sprintf(...)怎么用,它就怎么用。我只提醒使用時(shí)需注意一點(diǎn):就是它的參數(shù)的特殊性,由于編譯器在編譯時(shí)并不能去校驗(yàn)格式串參數(shù)與對(duì)應(yīng)的變?cè)念?lèi)型及長(zhǎng)度。所以你必須要注意,兩者一定要對(duì)應(yīng)上,
否則就會(huì)出錯(cuò)。如:
CString str;
int a = 12;
str.Format("first:%l, second: %s", a, "error");//result?試試
7 LockBuffer() 與 UnlockBuffer()
顧名思議,這兩個(gè)函數(shù)的作用就是對(duì)引用內(nèi)存塊進(jìn)行加鎖及解鎖。但使用它有什么作用及執(zhí)行過(guò)它后對(duì)CString串有什么實(shí)質(zhì)上的影響。其實(shí)挺簡(jiǎn)單,看下面代碼:
(1) CString str("test");
(2) str.LockBuffer();
(3) CString temp = str;
(4) str.UnlockBuffer();
(5) str.LockBuffer();
(6) str = "error";
(7) str.ReleaseBuffer();
執(zhí)行完(3)后,與通常情況下不同,temp與str并不指向同一引用內(nèi)存塊。你可以在watch窗口用這個(gè)表達(dá)式(CStringData*)((CStringData*)(str.m_pchData)-1)看看。
其實(shí)在msdn中有說(shuō)明:
While in a locked state, the string is protected in two ways:
No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string.
The locked string will never reference another string, even if that other string is copied to the locked string.
8 CString 只是處理串嗎?
不對(duì),CString不只是能操作串,而且還能處理內(nèi)存塊數(shù)據(jù)。功能完善吧!看這段代碼
char p[20];
for(int loop=0; loop<sizeof(p); loop++)
{
p[loop] = 10-loop;
}
CString str((LPCTSTR)p, 20);
char temp[20];
memcpy(temp, str, str.GetLength());
str完全能夠轉(zhuǎn)載內(nèi)存塊p到內(nèi)存塊temp中。所以能用CString來(lái)處理二進(jìn)制數(shù)據(jù)
8 AllocSysString()與SetSysString(BSTR*)
這兩個(gè)函數(shù)提供了串與BSTR的轉(zhuǎn)換。使用時(shí)須注意一點(diǎn):當(dāng)調(diào)用AllocSysString()后,須調(diào)用它SysFreeString(...)
9 參數(shù)的安全檢驗(yàn)
在MFC中提供了多個(gè)宏來(lái)進(jìn)行參數(shù)的安全檢查,如:ASSERT. 其中在CString中也不例外,有許多這樣的參數(shù)檢驗(yàn),其實(shí)這也說(shuō)明了代碼的安全性高,可有時(shí)我們會(huì)發(fā)現(xiàn)這很煩,也導(dǎo)致Debug與Release版本不一樣,如有時(shí)程序Debug通正常,而Release則程序崩潰;而有時(shí)恰相反,Debug不行,Release行。其實(shí)我個(gè)人認(rèn)為,我們對(duì)CString的使用過(guò)程中,應(yīng)力求代碼質(zhì)量高,不能在Debug版本中出現(xiàn)任何斷言框,哪怕release運(yùn)行似乎看起來(lái)一切正常。但很不安全。如下代碼:
(1) CString str("test");
(2) str.LockBuffer();
(3) LPTSTR temp = str.GetBuffer(10);
(4) strcpy(temp, "error");
(5) str.ReleaseBuffer();
(6) str.ReleaseBuffer();//執(zhí)行到此時(shí),Debug版本會(huì)彈出錯(cuò)框
10 CString的異常處理
我只想強(qiáng)調(diào)一點(diǎn):只有分配內(nèi)存時(shí),才有可能導(dǎo)致拋出CMemoryException.
同樣,在msdn中的函數(shù)聲明中,注有throw( CMemoryException)的函數(shù)都有重新分配或調(diào)整內(nèi)存的可能。
11 跨模塊時(shí)的Cstring。即一個(gè)DLL的接口函數(shù)中的參數(shù)為CString&時(shí),它會(huì)發(fā)生怎樣的現(xiàn)象。解答我遇到的問(wèn)題。我的問(wèn)題原來(lái)已經(jīng)發(fā)貼,地址為:
http://www.csdn.net/expert/topic/741/741921.xml?temp=.2283136
構(gòu)造一個(gè)這樣CString對(duì)象時(shí),如CString str,你可知道此時(shí)的str所指向的引用內(nèi)存塊嗎?也許你會(huì)認(rèn)為它指向NULL。其實(shí)不對(duì),如果這樣的話,CString所采用的引用機(jī)制管理內(nèi)存塊就會(huì)有麻煩了,所以CString在構(gòu)造一個(gè)空串的對(duì)象時(shí),它會(huì)指向一個(gè)固定的初始化地址,這塊數(shù)據(jù)的聲明如下:
AFX_STATIC_DATA int _afxInitData[] = {-1,0,0,0};
簡(jiǎn)要描述概括一下:當(dāng)某個(gè)CString對(duì)象串置空的話,如Empty(),CString a等,它的成員變量m_pchData就會(huì)指向_afxInitData這個(gè)變量的地址。當(dāng)這個(gè)CString對(duì)象生命周期結(jié)束時(shí),正常情況下它會(huì)去對(duì)所指向的引用內(nèi)存塊計(jì)數(shù)減1,如果引用計(jì)數(shù)為0(即沒(méi)有任何CString引用它時(shí)),則釋放這塊引用內(nèi)存。而現(xiàn)在的情況是如果CString所指向的引用內(nèi)存塊是初始化內(nèi)存塊時(shí),則不會(huì)釋放任何內(nèi)存。
說(shuō)了這么多,這與我遇到的問(wèn)題有什么關(guān)系呢?其實(shí)關(guān)系大著呢?其真正原因就是如果exe模塊與dll模塊有一個(gè)是static編譯連接的話。那么這個(gè)CString初始化數(shù)據(jù)在exe模塊與dll模塊中有不同的地址,因?yàn)閟tatic連接則會(huì)在本模塊中有一份源代碼的拷貝。另外一種情況,如果兩個(gè)模塊都是share連接的,CString的實(shí)現(xiàn)代碼則在另一個(gè)單獨(dú)的dll中實(shí)現(xiàn),而AFX_STATIC_DATA指定變量只裝一次,所以?xún)蓚€(gè)模塊中_afxInitData有相同的地址。
現(xiàn)在問(wèn)題完全明白了吧!你可以自己去演示一下。
__declspec (dllexport) void test(CString& str)
{
str = "abdefakdfj";//如果是static連接,并且傳入的str為空串的話,這里出錯(cuò)。
}