• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 29, comments - 16, trackbacks - 0, articles - 0
               :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

                使用C/C++語言開發(fā)軟件的程序員經(jīng)常碰到這樣的問題:有時(shí)候是程序編譯沒有問題,但是鏈接的時(shí)候總是報(bào)告函數(shù)不存在(經(jīng)典的LNK 2001錯(cuò)誤),有時(shí)候是程序編譯和鏈接都沒有錯(cuò)誤,但是只要調(diào)用庫中的函數(shù)就會(huì)出現(xiàn)堆棧異常。這些現(xiàn)象通常是出現(xiàn)在CC++的代碼混合使用的情況下或 在C++程序中使用第三方的庫的情況下(不是用C++語言開發(fā)的),其實(shí)這都是函數(shù)調(diào)用約定(Calling Convention)和函數(shù)名修飾(Decorated Name)規(guī)則惹的禍。函數(shù)調(diào)用方式?jīng)Q定了函數(shù)參數(shù)入棧的順序,是由調(diào)用者函數(shù)還是被調(diào)用函數(shù)負(fù)責(zé)清除棧中的參數(shù)等問題,而函數(shù)名修飾規(guī)則決定了編譯器使 用何種名字修飾方式來區(qū)分不同的函數(shù),如果函數(shù)之間的調(diào)用約定不匹配或者名字修飾不匹配就會(huì)產(chǎn)生以上的問題。本文分別對CC++這兩種編程語言的函數(shù)調(diào) 用約定和函數(shù)名修飾規(guī)則進(jìn)行詳細(xì)的解釋,比較了它們的異同之處,并舉例說明了以上問題出現(xiàn)的原因。

            函數(shù)調(diào)用約定(Calling Convention

                函數(shù)調(diào)用約定不僅決定了發(fā)生函數(shù)調(diào)用時(shí)函數(shù)參數(shù)的入棧順序,還決定了是由調(diào)用者函數(shù)還是被調(diào)用函數(shù)負(fù)責(zé)清除棧中的參數(shù),還原堆棧。函數(shù)調(diào)用約定有很多方 式,除了常見的__cdecl__fastcall__stdcall之外,C++的編譯器還支持thiscall方式,不少C/C++編譯器還支持 naked call方式。這么多函數(shù)調(diào)用約定常常令許多程序員很迷惑,到底它們是怎么回事,都是在什么情況下使用呢?下面就分別介紹這幾種函數(shù)調(diào)用約定。


            1.__cdecl

                編譯器的命令行參數(shù)是/Gd。__cdecl方式是C/C++編譯器默認(rèn)的函數(shù)調(diào)用約定,所有非C++成員函數(shù)和那些沒有用__stdcall __fastcall聲明的函數(shù)都默認(rèn)是__cdecl方式,它使用C函數(shù)調(diào)用方式,函數(shù)參數(shù)按照從右向左的順序入棧,函數(shù)調(diào)用者負(fù)責(zé)清除棧中的參數(shù),由 于每次函數(shù)調(diào)用都要由編譯器產(chǎn)生清除(還原)堆棧的代碼,所以使用__cdecl方式編譯的程序比使用__stdcall方式編譯的程序要大很多,但是 __cdecl調(diào)用方式是由函數(shù)調(diào)用者負(fù)責(zé)清除棧中的函數(shù)參數(shù),所以這種方式支持可變參數(shù),比如printfwindowsAPI wsprintf就是__cdecl調(diào)用方式。對于C函數(shù),__cdecl方式的名字修飾約定是在函數(shù)名稱前添加一個(gè)下劃線;對于C++函數(shù),除非特別使 用extern "C",C++函數(shù)使用不同的名字修飾方式。


            2.__fastcall

                編譯器的命令行參數(shù)是/Gr__fastcall函數(shù)調(diào)用約定在可能的情況下使用寄存器傳遞參數(shù),通常是前兩個(gè) DWORD類型的參數(shù)或較小的參數(shù)使用ECXEDX寄存器傳遞,其余參數(shù)按照從右向左的順序入棧,被調(diào)用函數(shù)在返回之前負(fù)責(zé)清除棧中的參數(shù)。編譯器使用 兩個(gè)@修飾函數(shù)名字,后跟十進(jìn)制數(shù)表示的函數(shù)參數(shù)列表大小,例如:@function_name@number。需要注意的是__fastcall函數(shù)調(diào) 用約定在不同的編譯器上可能有不同的實(shí)現(xiàn),比如16位的編譯器和32位的編譯器,另外,在使用內(nèi)嵌匯編代碼時(shí),還要注意不能和編譯器使用的寄存器有沖突。


            3.__stdcall
             
               
            編譯器的命令行參數(shù)是/Gz,__stdcallPascal程序的缺省調(diào)用方式,大多數(shù)WindowsAPI也是__stdcall調(diào)用約定。 __stdcall函數(shù)調(diào)用約定將函數(shù)參數(shù)從右向左入棧,除非使用指針或引用類型的參數(shù),所有參數(shù)采用傳值方式傳遞,由被調(diào)用函數(shù)負(fù)責(zé)清除棧中的參數(shù)。對 于C函數(shù),__stdcall的名稱修飾方式是在函數(shù)名字前添加下劃線,在函數(shù)名字后添加@和函數(shù)參數(shù)的大小,例如:_functionname@number

            4.thiscall

                thiscall只用在C++成員函數(shù)的調(diào)用,函數(shù)參數(shù)按照從右向左的順序入棧,類實(shí)例的this指針通過ECX寄存器傳遞。需要注意的是thiscall不是C++的關(guān)鍵字,不能使用thiscall聲明函數(shù),它只能由編譯器使用。

            5.naked call

                采用前面幾種函數(shù)調(diào)用約定的函數(shù),編譯器會(huì)在必要的時(shí)候自動(dòng)在函數(shù)開始添加保存ESI,EDI,EBX,EBP寄存器的代碼,在退出函數(shù)時(shí)恢復(fù)這些寄存器 的內(nèi)容,使用naked call方式聲明的函數(shù)不會(huì)添加這樣的代碼,這也就是為什么稱其為naked的原因吧。naked  call不是類型修飾符,故必須和_declspec共同使用。

                VC的編譯環(huán)境默認(rèn)是使用__cdecl調(diào)用約定,也可以在編譯環(huán)境的Project Setting...菜單-》C/C++ =》Code  Generation項(xiàng)選擇設(shè)置函數(shù)調(diào)用約定。也可以直接在函數(shù)聲明前添加關(guān)鍵字__stdcall、__cdecl__fastcall等單獨(dú)確定函 數(shù)的調(diào)用方式。在Windows系統(tǒng)上開發(fā)軟件常用到WINAPI宏,它可以根據(jù)編譯設(shè)置翻譯成適當(dāng)?shù)暮瘮?shù)調(diào)用約定,在WIN32中,它被定義為 __stdcall 

             

            函數(shù)名字修飾(Decorated Name)方式

                函數(shù)的名字修飾(Decorated Name)就是編譯器在編譯期間創(chuàng)建的一個(gè)字符串,用來指明函數(shù)的定義或原型。LINK程序或其他工具有時(shí)需要指定函數(shù)的名字修飾來定位函數(shù)的正確位置。 多數(shù)情況下程序員并不需要知道函數(shù)的名字修飾,LINK程序或其他工具會(huì)自動(dòng)區(qū)分他們。當(dāng)然,在某些情況下需要指定函數(shù)的名字修飾,例如在C++程序中, 為了讓LINK程序或其他工具能夠匹配到正確的函數(shù)名字,就必須為重載函數(shù)和一些特殊的函數(shù)(如構(gòu)造函數(shù)和析構(gòu)函數(shù))指定名字裝飾。另一種需要指定函數(shù)的 名字修飾的情況是在匯編程序中調(diào)用CC++的函數(shù)。如果函數(shù)名字,調(diào)用約定,返回值類型或函數(shù)參數(shù)有任何改變,原來的名字修飾就不再有效,必須指定新的 名字修飾。CC++程序的函數(shù)在內(nèi)部使用不同的名字修飾方式,下面將分別介紹這兩種方式。

            1. C編譯器的函數(shù)名修飾規(guī)則

                對于__stdcall調(diào)用約定,編譯器和鏈接器會(huì)在輸出函數(shù)名前加上一個(gè)下劃線前綴,函數(shù)名后面加上一個(gè)“@”符號和其參數(shù)的字節(jié)數(shù),例如_functionname@number。__cdecl調(diào)用約定僅在輸出函數(shù)名前加上一個(gè)下劃線前綴,例如_functionname__fastcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)“@”符號,后面也是一個(gè)“@”符號和其參數(shù)的字節(jié)數(shù),例如@functionname@number。   
             
            2. C++
            編譯器的函數(shù)名修飾規(guī)則

                C++的函數(shù)名修飾規(guī)則有些復(fù)雜,但是信息更充分,通過分析修飾名不僅能夠知道函數(shù)的調(diào)用方式,返回值類型,參數(shù)個(gè)數(shù)甚至參數(shù)類型。不管 __cdecl,__fastcall還是__stdcall調(diào)用方式,函數(shù)修飾都是以一個(gè)“?”開始,后面緊跟函數(shù)的名字,再后面是參數(shù)表的開始標(biāo)識(shí)和 按照參數(shù)類型代號拼出的參數(shù)表。對于__stdcall方式,參數(shù)表的開始標(biāo)識(shí)是“@@YG,對于__cdecl方式則是“@@YA,對于__fastcall方式則是“@@YI。參數(shù)表的拼寫代號如下所示:
            X--void   
            D--char   
            E--unsigned char   
            F--short   
            H--int   
            I--unsigned int   
            J--long   
            K--unsigned long
            DWORD
            M--float   
            N--double   
            _N--bool
            U--struct
            ....
            指 針的方式有些特別,用PA表示指針,用PB表示const類型的指針。后面的代號表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以0”代替,一個(gè)0”代 表一次重復(fù)。U表示結(jié)構(gòu)類型,通常后跟結(jié)構(gòu)體的類型名,用“@@”表示結(jié)構(gòu)類型名的結(jié)束。函數(shù)的返回值不作特殊處理,它的描述方式和函數(shù)參數(shù)一樣,緊跟著 參數(shù)表的開始標(biāo)志,也就是說,函數(shù)參數(shù)表的第一項(xiàng)實(shí)際上是表示函數(shù)的返回值類型。參數(shù)表后以“@Z標(biāo)識(shí)整個(gè)名字的結(jié)束,如果該函數(shù)無參數(shù),則以“Z”標(biāo)識(shí)結(jié)束。下面舉兩個(gè)例子,假如有以下函數(shù)聲明:

            int Function1(char *var1,unsigned long);

            其函數(shù)修飾名為“?Function1@@YGHPADK@Z,而對于函數(shù)聲明:

            void Function2();

            其函數(shù)修飾名則為“?Function2@@YGXXZ。

             

                對于C++的類成員函數(shù)(其調(diào)用方式是thiscall),函數(shù)的名字修飾與非成員的C++函數(shù)稍有不同,首先就是在函數(shù)名字和參數(shù)表之間插入以“@”字符引導(dǎo)的類名;其次是參數(shù)表的開始標(biāo)識(shí)不同,公有(public)成員函數(shù)的標(biāo)識(shí)是“@@QAE”,保護(hù)(protected)成員函數(shù)的標(biāo)識(shí)是“@@IAE”,私有(private)成員函數(shù)的標(biāo)識(shí)是“@@AAE,如果函數(shù)聲明使用了const關(guān)鍵字,則相應(yīng)的標(biāo)識(shí)應(yīng)分別為“@@QBE,“@@IBE“@@ABE。如果參數(shù)類型是類實(shí)例的引用,則使用“AAV1”,對于const類型的引用,則使用“ABV1”。下面就以類CTest為例說明C++成員函數(shù)的名字修飾規(guī)則:

            class CTest
            {
            ......
            private:
                void Function(int);
            protected:
                void CopyInfo(const CTest &src);
            public:
                long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);
                long InsightClass(DWORD dwClass) const;
            ......
            };

            對于成員函數(shù)Function,其函數(shù)修飾名為“?Function@CTest@@AAEXH@Z,字符串“@@AAE表示這是一個(gè)私有函數(shù)。成員函數(shù)CopyInfo只有一個(gè)參數(shù),是對類CTestconst引用參數(shù),其函數(shù)修飾名為“?CopyInfo@CTest@@IAEXABV1@@Z。 DrawText是一個(gè)比較復(fù)雜的函數(shù)聲明,不僅有字符串參數(shù),還有結(jié)構(gòu)體參數(shù)和HDC句柄參數(shù),需要指出的是HDC實(shí)際上是一個(gè)HDC__結(jié)構(gòu)類型的指 針,這個(gè)參數(shù)的表示就是“PAUHDC__@@”,其完整的函數(shù)修飾名為“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z。InsightClass是一個(gè)共有的const函數(shù),它的成員函數(shù)標(biāo)識(shí)是“@@QBE,完整的修飾名就是“?InsightClass@CTest@@QBEJK@Z

            無論是C函數(shù)名修飾方式還是C++函數(shù)名修飾方式均不改變輸出函數(shù)名中的字符大小寫,這和PASCAL調(diào)用約定不同,PASCAL約定輸出的函數(shù)名無任何修飾且全部大寫。

            3.查看函數(shù)的名字修飾

                有兩種方式可以檢查你的程序中的函數(shù)的名字修飾:使用編譯輸出列表或使用Dumpbin工具。使用/FAc/FAs/FAcs命令行參數(shù)可以讓編譯器 輸出函數(shù)或變量名字列表。使用dumpbin.exe /SYMBOLS命令也可以獲得obj文件或lib文件中的函數(shù)或變量名字列表。此外,還可以使用 undname.exe 將修飾名轉(zhuǎn)換為未修飾形式。

             

            函數(shù)調(diào)用約定和名字修飾規(guī)則不匹配引起的常見問題

                函數(shù)調(diào)用時(shí)如果出現(xiàn)堆棧異常,十有八九是由于函數(shù)調(diào)用約定不匹配引起的。比如動(dòng)態(tài)鏈接庫a有以下導(dǎo)出函數(shù):

            long MakeFun(long lFun);

            動(dòng)態(tài)庫生成的時(shí)候采用的函數(shù)調(diào)用約定是__stdcall,所以編譯生成的a.dll中函數(shù)MakeFun的調(diào)用約 定是_stdcall,也就是函數(shù)調(diào)用時(shí)參數(shù)從右向左入棧,函數(shù)返回時(shí)自己還原堆?!,F(xiàn)在某個(gè)程序模塊b要引用a中的MakeFun,ba一樣使用 C++方式編譯,只是b模塊的函數(shù)調(diào)用方式是__cdecl,由于b包含了a提供的頭文件中MakeFun函數(shù)聲明,所以MakeFunb模塊中被其它 調(diào)用MakeFun的函數(shù)認(rèn)為是__cdecl調(diào)用方式,b模塊中的這些函數(shù)在調(diào)用完MakeFun當(dāng)然要幫著恢復(fù)堆棧啦,可是MakeFun已經(jīng)在結(jié)束 時(shí)自己恢復(fù)了堆棧,b模塊中的函數(shù)這樣多此一舉就引起了棧指針錯(cuò)誤,從而引發(fā)堆棧異常。宏觀上的現(xiàn)象就是函數(shù)調(diào)用沒有問題(因?yàn)閰?shù)傳遞順序是一樣 的),MakeFun也完成了自己的功能,只是函數(shù)返回后引發(fā)錯(cuò)誤。解決的方法也很簡單,只要保證兩個(gè)模塊的在編譯時(shí)設(shè)置相同的函數(shù)調(diào)用約定就行了。

             

                在了解了函數(shù)調(diào)用約定和函數(shù)的名修飾規(guī)則之后,再來看在C++程序中使用C語言編譯的庫時(shí)經(jīng)常出現(xiàn)的LNK 2001錯(cuò)誤就很簡單了。還以上面例子的兩個(gè)模塊為例,這一次兩個(gè)模塊在編譯的時(shí)候都采用__stdcall調(diào)用約定,但是a.dll使用C語言的語法編 譯的(C語言方式),所以a.dll的載入庫a.libMakeFun函數(shù)的名字修飾就是“_MakeFun@4。b包含了a提供的頭文件中MakeFun函數(shù)聲明,但是由于b采用的是C++語言編譯,所以MakeFunb模塊中被按照C++的名字修飾規(guī)則命名為“?MakeFun@@YGJJ@Z,編譯過程相安無事,鏈接程序時(shí)c++的鏈接器就到a.lib中去找“?MakeFun@@YGJJ@Z,但是a.lib中只有“_MakeFun@4,沒有“?MakeFun@@YGJJ@Z,于是鏈接器就報(bào)告:

            error LNK2001: unresolved external symbol ?MakeFun@@YGJJ@Z

            解決的方法和簡單,就是要讓b模塊知道這個(gè)函數(shù)是C語言編譯的,extern "C"可以做到這一點(diǎn)。一個(gè)采用C語言編譯的庫應(yīng)該考慮到使用這個(gè)庫的程序可能是C++程序(使用C++編譯器),所以在設(shè)計(jì)頭文件時(shí)應(yīng)該注意這一點(diǎn)。通常應(yīng)該這樣聲明頭文件:

            #ifdef _cplusplus
            extern "C" {
            #endif

            long MakeFun(long lFun);

            #ifdef _cplusplus
            }
            #endif

            這樣C++的編譯器就知道MakeFun的修飾名是“_MakeFun@4,就不會(huì)有鏈接錯(cuò)誤了。

                許多人不明白,為什么我使用的編譯器都是VC的編譯器還會(huì)產(chǎn)生“error LNK2001”錯(cuò)誤?其實(shí),VC的編譯器會(huì)根據(jù)源文件的擴(kuò)展名選擇編譯方式,如果文件的擴(kuò)展名是“.C”,編譯器會(huì)采用C的語法編譯,如果擴(kuò)展名是 “.cpp”,編譯器會(huì)使用C++的語法編譯程序,所以,最好的方法就是使用extern "C"。

             

            posted @ 2009-02-15 10:43 王勇良 閱讀(1103) | 評論 (0)編輯 收藏

            1. ASCII碼

            我們知道,在計(jì)算機(jī)內(nèi)部,所有的信息最終都表示為一個(gè)二進(jìn)制的字符串。每一個(gè)二進(jìn)制位(bit)有0和1兩種狀態(tài),因此八個(gè)二進(jìn)制位就可以組合出 256種狀態(tài),這被稱為一個(gè)字節(jié)(byte)。也就是說,一個(gè)字節(jié)一共可以用來表示256種不同的狀態(tài),每一個(gè)狀態(tài)對應(yīng)一個(gè)符號,就是256個(gè)符號,從 0000000到11111111。

            上個(gè)世紀(jì)60年代,美國制定了一套字符編碼,對英語字符與二進(jìn)制位之間的關(guān)系,做了統(tǒng)一規(guī)定。這被稱為ASCII碼,一直沿用至今。

            ASCII碼一共規(guī)定了128個(gè)字符的編碼,比如空格“SPACE”是32(二進(jìn)制00100000),大寫的字母A是65(二進(jìn)制01000001)。這128個(gè)符號(包括32個(gè)不能打印出來的控制符號),只占用了一個(gè)字節(jié)的后面7位,最前面的1位統(tǒng)一規(guī)定為0。

            2、非ASCII編碼

            英語用128個(gè)符號編碼就夠了,但是用來表示其他語言,128個(gè)符號是不夠的。比如,在法語中,字母上方有注音符號,它就無法用ASCII碼表示。 于是,一些歐洲國家就決定,利用字節(jié)中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進(jìn)制10000010)。這樣一來,這些歐洲國家使 用的編碼體系,可以表示最多256個(gè)符號。

            但是,這里又出現(xiàn)了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個(gè)符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼 中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會(huì)代表另一個(gè)符號。但是不管怎樣,所有這些編碼方式中,0—127表示的符號是一樣的,不一樣的只是128—255的這一段。

            至于亞洲國家的文字,使用的符號就更多了,漢字就多達(dá)10萬左右。一個(gè)字節(jié)只能表示256種符號,肯定是不夠的,就必須使用多個(gè)字節(jié)表達(dá)一個(gè)符號。 比如,簡體中文常見的編碼方式是GB2312,使用兩個(gè)字節(jié)表示一個(gè)漢字,所以理論上最多可以表示256x256=65536個(gè)符號。

            中文編碼的問題需要專文討論,這篇筆記不涉及。這里只指出,雖然都是用多個(gè)字節(jié)表示一個(gè)符號,但是GB類的漢字編碼與后文的Unicode和UTF-8是毫無關(guān)系的。

            3.Unicode

            正如上一節(jié)所說,世界上存在著多種編碼方式,同一個(gè)二進(jìn)制數(shù)字可以被解釋成不同的符號。因此,要想打開一個(gè)文本文件,就必須知道它的編碼方式,否則用錯(cuò)誤的編碼方式解讀,就會(huì)出現(xiàn)亂碼。為什么電子郵件常常出現(xiàn)亂碼?就是因?yàn)榘l(fā)信人和收信人使用的編碼方式不一樣。

            可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個(gè)符號都給予一個(gè)獨(dú)一無二的編碼,那么亂碼問題就會(huì)消失。這就是Unicode,就像它的名字都表示的,這是一種所有符號的編碼。

            Unicode當(dāng)然是一個(gè)很大的集合,現(xiàn)在的規(guī)??梢匀菁{100多萬個(gè)符號。每個(gè)符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字“嚴(yán)”。具體的符號對應(yīng)表,可以查詢unicode.org,或者專門的漢字對應(yīng)表

            4. Unicode的問題

            需要注意的是,Unicode只是一個(gè)符號集,它只規(guī)定了符號的二進(jìn)制代碼,卻沒有規(guī)定這個(gè)二進(jìn)制代碼應(yīng)該如何存儲(chǔ)。

            比如,漢字“嚴(yán)”的unicode是十六進(jìn)制數(shù)4E25,轉(zhuǎn)換成二進(jìn)制數(shù)足足有15位(100111000100101),也就是說這個(gè)符號的表示至少需要2個(gè)字節(jié)。表示其他更大的符號,可能需要3個(gè)字節(jié)或者4個(gè)字節(jié),甚至更多。

            這里就有兩個(gè)嚴(yán)重的問題,第一個(gè)問題是,如何才能區(qū)別unicode和ascii?計(jì)算機(jī)怎么知道三個(gè)字節(jié)表示一個(gè)符號,而不是分別表示三個(gè)符號 呢?第二個(gè)問題是,我們已經(jīng)知道,英文字母只用一個(gè)字節(jié)表示就夠了,如果unicode統(tǒng)一規(guī)定,每個(gè)符號用三個(gè)或四個(gè)字節(jié)表示,那么每個(gè)英文字母前都必 然有二到三個(gè)字節(jié)是0,這對于存儲(chǔ)來說是極大的浪費(fèi),文本文件的大小會(huì)因此大出二三倍,這是無法接受的。

            它們造成的結(jié)果是:1)出現(xiàn)了unicode的多種存儲(chǔ)方式,也就是說有許多種不同的二進(jìn)制格式,可以用來表示unicode。2)unicode在很長一段時(shí)間內(nèi)無法推廣,直到互聯(lián)網(wǎng)的出現(xiàn)。

            5.UTF-8

            互聯(lián)網(wǎng)的普及,強(qiáng)烈要求出現(xiàn)一種統(tǒng)一的編碼方式。UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種unicode的實(shí)現(xiàn)方式。其他實(shí)現(xiàn)方式還包括UTF-16和UTF-32,不過在互聯(lián)網(wǎng)上基本不用。重復(fù)一遍,這里的關(guān)系是,UTF-8是Unicode的實(shí)現(xiàn)方式之一。

            UTF-8最大的一個(gè)特點(diǎn),就是它是一種變長的編碼方式。它可以使用1~4個(gè)字節(jié)表示一個(gè)符號,根據(jù)不同的符號而變化字節(jié)長度。

            UTF-8的編碼規(guī)則很簡單,只有二條:

            1)對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號的unicode碼。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。

            2)對于n字節(jié)的符號(n>1),第一個(gè)字節(jié)的前n位都設(shè)為1,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位,全部為這個(gè)符號的unicode碼。

            下表總結(jié)了編碼規(guī)則,字母x表示可用編碼的位。

            Unicode符號范圍 | UTF-8編碼方式
            (十六進(jìn)制) | (二進(jìn)制)
            --------------------+---------------------------------------------
            0000 0000-0000 007F | 0xxxxxxx
            0000 0080-0000 07FF | 110xxxxx 10xxxxxx
            0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
            0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

            下面,還是以漢字“嚴(yán)”為例,演示如何實(shí)現(xiàn)UTF-8編碼。

            已知“嚴(yán)”的unicode是4E25(100111000100101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800-0000 FFFF),因此“嚴(yán)”的UTF-8編碼需要三個(gè)字節(jié),即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,從“嚴(yán)”的最后一個(gè)二進(jìn)制位開始,依次從后向前填入格式中的x,多出的位補(bǔ)0。這樣就得到了,“嚴(yán)”的UTF-8編碼是 “11100100 10111000 10100101”,轉(zhuǎn)換成十六進(jìn)制就是E4B8A5。

            6. Unicode與UTF-8之間的轉(zhuǎn)換

            通過上一節(jié)的例子,可以看到“嚴(yán)”的Unicode碼是4E25,UTF-8編碼是E4B8A5,兩者是不一樣的。它們之間的轉(zhuǎn)換可以通過程序?qū)崿F(xiàn)。

            在Windows平臺(tái)下,有一個(gè)最簡單的轉(zhuǎn)化方法,就是使用內(nèi)置的記事本小程序Notepad.exe。打開文件后,點(diǎn)擊“文件”菜單中的“另存為”命令,會(huì)跳出一個(gè)對話框,在最底部有一個(gè)“編碼”的下拉條。

            bg2007102801.jpg

            里面有四個(gè)選項(xiàng):ANSI,Unicode,Unicode big endian 和 UTF-8。

            1)ANSI是默認(rèn)的編碼方式。對于英文文件是ASCII編碼,對于簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會(huì)采用Big5碼)。

            2)Unicode編碼指的是UCS-2編碼方式,即直接用兩個(gè)字節(jié)存入字符的Unicode碼。這個(gè)選項(xiàng)用的little endian格式。

            3)Unicode big endian編碼與上一個(gè)選項(xiàng)相對應(yīng)。我在下一節(jié)會(huì)解釋little endian和big endian的涵義。

            4)UTF-8編碼,也就是上一節(jié)談到的編碼方法。

            選擇完”編碼方式“后,點(diǎn)擊”保存“按鈕,文件的編碼方式就立刻轉(zhuǎn)換好了。

            7. Little endian和Big endian

            上一節(jié)已經(jīng)提到,Unicode碼可以采用UCS-2格式直接存儲(chǔ)。以漢字”嚴(yán)“為例,Unicode碼是4E25,需要用兩個(gè)字節(jié)存儲(chǔ),一個(gè)字節(jié) 是4E,另一個(gè)字節(jié)是25。存儲(chǔ)的時(shí)候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

            這兩個(gè)古怪的名稱來自英國作家斯威夫特的《格列佛游記》。在該書中,小人國里爆發(fā)了內(nèi)戰(zhàn),戰(zhàn)爭起因是人們爭論,吃雞蛋時(shí)究竟是從大頭(Big- Endian)敲開還是從小頭(Little-Endian)敲開。為了這件事情,前后爆發(fā)了六次戰(zhàn)爭,一個(gè)皇帝送了命,另一個(gè)皇帝丟了王位。

            因此,第一個(gè)字節(jié)在前,就是”大頭方式“(Big endian),第二個(gè)字節(jié)在前就是”小頭方式“(Little endian)。

            那么很自然的,就會(huì)出現(xiàn)一個(gè)問題:計(jì)算機(jī)怎么知道某一個(gè)文件到底采用哪一種方式編碼?

            Unicode規(guī)范中定義,每一個(gè)文件的最前面分別加入一個(gè)表示編碼順序的字符,這個(gè)字符的名字叫做”零寬度非換行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個(gè)字節(jié),而且FF比FE大1。

            如果一個(gè)文本文件的頭兩個(gè)字節(jié)是FE FF,就表示該文件采用大頭方式;如果頭兩個(gè)字節(jié)是FF FE,就表示該文件采用小頭方式。

            8. 實(shí)例

            下面,舉一個(gè)實(shí)例。

            打開”記事本“程序Notepad.exe,新建一個(gè)文本文件,內(nèi)容就是一個(gè)”嚴(yán)“字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8編碼方式保存。

            然后,用文本編輯軟件UltraEdit中的”十六進(jìn)制功能“,觀察該文件的內(nèi)部編碼方式。

            1)ANSI:文件的編碼就是兩個(gè)字節(jié)“D1 CF”,這正是“嚴(yán)”的GB2312編碼,這也暗示GB2312是采用大頭方式存儲(chǔ)的。

            2)Unicode:編碼是四個(gè)字節(jié)“FF FE 25 4E”,其中“FF FE”表明是小頭方式存儲(chǔ),真正的編碼是4E25。

            3)Unicode big endian:編碼是四個(gè)字節(jié)“FE FF 4E 25”,其中“FE FF”表明是大頭方式存儲(chǔ)。

            4)UTF-8:編碼是六個(gè)字節(jié)“EF BB BF E4 B8 A5”,前三個(gè)字節(jié)“EF BB BF”表示這是UTF-8編碼,后三個(gè)“E4B8A5”就是“嚴(yán)”的具體編碼,它的存儲(chǔ)順序與編碼順序是一致的。

            9. 延伸閱讀

            * The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets(關(guān)于字符集的最基本知識(shí))

            * 談?wù)刄nicode編碼

            * RFC3629:UTF-8, a transformation format of ISO 10646(如果實(shí)現(xiàn)UTF-8的規(guī)定)

            posted @ 2009-02-15 10:38 王勇良 閱讀(836) | 評論 (0)編輯 收藏

            如果你用C++來編寫COM,那么你將必不可少的使用這三個(gè)類型。使用這三種wrapper class毫無疑問會(huì)簡化我們的編程,使得使用SAFEARRAY, VARIANT和BSTR簡單。但是,使用這三個(gè)類型依然需要小心,因?yàn)槭褂貌划?dāng)?shù)脑挘蜁?huì)造成內(nèi)存泄漏,或效率降低。

            1. 如果拷貝兩個(gè)BSTR
            假如我們一個(gè)BSTR,這個(gè)時(shí)候我希望復(fù)制一份BSTR,并丟棄之前的BSTR。通常我們會(huì)這么寫:
            CComBSTR StringToBSTR(const string & sVal)
            {
                 CComBSTR bstrValue 
            = sVal.data();
                 
            return bstrValue;
            }

            int main()
            {
                 CComBSTR vValue 
            = StringToBSTR("value");

                 
            return 0;
            }

            當(dāng)然,上面這個(gè)程序沒有任何問題,不會(huì)有任何內(nèi)存泄漏的可能。但是,你有沒有上面代碼里都發(fā)生了什么了?
            答案很簡單,在函數(shù)StringToBSTR里面,講bstrValue返回的時(shí)候,會(huì)調(diào)用CComBSTR::Copy(),在Copy()里面將會(huì)調(diào)用
             ::SysAllocStringByteLen()
            這個(gè)函數(shù)。而后在給vValue賦值的時(shí)候,又 會(huì)調(diào)用一次
            ::SysAllocString()
            顯而易見,開銷很大。

            那么,我們將怎么改進(jìn)這段代碼了?
            BSTR StringToBSTR(const string & sVal)
            {
                 CComBSTR bstrValue 
            = sVal.data();
                 
            return bstrValue.Detach();
            }

            int main()
            {
                 CComBSTR vValue.Attach(StringToBSTR(
            "value"));

                 
            return 0;
            }
            這樣,通過CComBSTR::Detach(),我們將BSTR返回回來,通過CComBSTR::Attach(),我們將BSTR指針存儲(chǔ)起來。這樣,就減小了兩次開銷,大大提高了效率,也不會(huì)造成內(nèi)存效率。

            2. 如何使用CComSafeArray
            使 用CComSafeArray的一個(gè)最大的好處,就是它會(huì)自動(dòng)釋放元素是VARIANT和BSTR。也就是說,如果你的類型是VARIANT,它會(huì)自動(dòng)調(diào) 用::VariantClear()。如果你的類型是BSTR,他會(huì)自動(dòng)調(diào)用::SysStringFree()方法。但是使用它的時(shí)候,同樣要小心。
            2.1 成對使用::SafeArrayAccessData()和::SafeArrayUnaccessData()
            我們有時(shí)候會(huì)這樣使用CComSafeArray的元素:
            void DoSomething()
            {
                 CComSafeArray
            <double> pSafeArray(3);
                 
            double * pVal = NULL;
                 ::SafeArrayAccessData(pSafeArray
            .m_psa, (void**)&pVal);

                 
            //handle the elements through the pVal;
            }
            因?yàn)?:SafeArrayAccessData 方法會(huì)在SFAEARRAY上給lock加1. 如果上面程序顯示調(diào)用CComSafeArray::Destroy()函數(shù),你檢查它返回來的HRESULT的時(shí)候,應(yīng)該是下面的值:
                    hr    0x8002000d 內(nèi)存已鎖定。     HRESULT
            如果你不仔細(xì)檢查,那么將造成CComSafeArray沒有釋放。
            2.2 從CComSafeArray轉(zhuǎn)為成CComVariant
            有時(shí)候我們使用CComVariant包裝SAFEARRY。你會(huì)這樣寫代碼:
            void DoSomething()
            {
                 CComSafeArray
            <double> pSafeArray(3);
                
                 
            //fill the safearray

                 CComVariant v 
            = pSafeArray.Detach();
            }
            你可能會(huì)任務(wù)CComVariant會(huì)存儲(chǔ)pSafeArray的指針??上В沐e(cuò)了。
            CComVariant會(huì)調(diào)用::SafeArrayCopy 來完成賦值操作。而你的pSafeArray已經(jīng)調(diào)用了Detach()操作,那么它里面的SAFEARRAY就變成了孤兒,沒有人去釋放它了。
            那么你應(yīng)該怎么寫了?
            你可以這么寫:
            void DoSomething()
            {
                 CComSafeArray
            <double> pSafeArray(3);
                
                 
            //fill the safearray

                 CComVariant v 
            = pSafeArray.m_psa;
            }
            這樣,CComVariant會(huì)調(diào)用::SafeArrayCopy 來完成復(fù)制操作,而CComSafeArray也會(huì)保證在析構(gòu)的時(shí)候釋放里面的SAFEARRAY。

            使用上面三個(gè)wrapper類,確實(shí)可以很方便我們編程,也能避免很多memory leak。但是,使用他們同樣要小心,不然,同樣會(huì)造成性能損失,或者,更糟糕的,內(nèi)存泄漏。

            posted @ 2009-02-15 10:37 王勇良 閱讀(2606) | 評論 (0)編輯 收藏

            一 static 產(chǎn)生背景

            引出原因:函數(shù)內(nèi)部定義的變量,在程序執(zhí)行到它的定義處時(shí),編譯器為它在棧上分配空間,大家知道,函數(shù)

            在棧上分配的空間在此函數(shù)執(zhí)行結(jié)束時(shí)會(huì)釋放掉,這樣就產(chǎn)生了一個(gè)問題: 如果想將函數(shù)中此變量的值保存至

            下一次調(diào)用時(shí),如何實(shí)現(xiàn)?

            最容易想到的方法是定義一個(gè)全局的變量,但定義為一個(gè)全局變量有許多缺點(diǎn),最明顯的缺點(diǎn)是破壞了此變量

            的訪問范圍(使得在此函數(shù)中定義的變量,不僅僅受此函數(shù)控制)。

                   類的靜態(tài)成員也是這個(gè)道理。


            解決方案:因此C++ 中引入了static,用它來修飾變量,它能夠指示編譯

            器將此變量在程序的靜態(tài)存儲(chǔ)區(qū)分配空間保存,這樣即實(shí)現(xiàn)了目的,又使得此變量的存取范圍不變。


            2) 具體作用

            Static作用分析總結(jié):static總是使得變量或?qū)ο蟮拇鎯?chǔ)形式變成靜態(tài)存儲(chǔ),連接方式變成內(nèi)部連接,對于局

            部變量(已經(jīng)是內(nèi)部連接了),它僅改變其存儲(chǔ)方式;對于全局變量(已經(jīng)是靜態(tài)存儲(chǔ)了),它僅改變其連接

            類型。(1 連接方式:成為內(nèi)部連接;2 存儲(chǔ)形式:存放在靜態(tài)全局存儲(chǔ)區(qū))
            二 const 產(chǎn)生背景

            a) C++有一個(gè)類型嚴(yán)格的編譯系統(tǒng),這使得C++程序的錯(cuò)誤在編譯階段即可發(fā)現(xiàn)許多,從而使得出錯(cuò)率大為減少

            ,因此,也成為了C++與C相比,有著突出優(yōu)點(diǎn)的一個(gè)方面。

            b) C中很常見的預(yù)處理指令 #define VariableName VariableValue 可以很方便地進(jìn)行值替代,這種值替代至

            少在三個(gè)方面優(yōu)點(diǎn)突出:

            一是避免了意義模糊的數(shù)字出現(xiàn),使得程序語義流暢清晰,如下例:

              #define USER_NUM_MAX 107 這樣就避免了直接使用107帶來的困惑。

            二是可以很方便地進(jìn)行參數(shù)的調(diào)整與修改,如上例,當(dāng)人數(shù)由107變?yōu)?01時(shí),改動(dòng)此處即可;

            三是提高了程序的執(zhí)行效率,由于使用了預(yù)編譯器進(jìn)行值替代,并不需要為這些常量分配存儲(chǔ)空間,所以執(zhí)行

            的效率較高。

            然而,預(yù)處理語句雖然有以上的許多優(yōu)點(diǎn),但它有個(gè)比較致命的缺點(diǎn),即,預(yù)處理語
            句僅僅只是簡單值替代,缺乏類型的檢測機(jī)制。這樣預(yù)處理語句就不能享受C++嚴(yán)
            格類型檢查的好處,從而可能成為引發(fā)一系列錯(cuò)誤的隱患。


            Const 推出的初始目的,正是為了取代預(yù)編譯指令,消除它的缺點(diǎn),同時(shí)

            繼承它的優(yōu)點(diǎn)。

            現(xiàn)在它的形式變成了:

            Const DataType VariableName = VariableValue ;

            2) 具體作用

            1.const 用于指針的兩種情況分析:

             
            int const *A;  //A可變,*A不可變

             
            int *const A;  //A不可變,*A可變

             分析:const 是一個(gè)左結(jié)合的類型修飾符,它與其左側(cè)的類型修飾符和為一個(gè)

            類型修飾符,所以,int const 限定 *A,不限定A。int *const 限定A,不限定*A。


            2.const 限定函數(shù)的傳遞值參數(shù):

             
            void Fun(const int Var);
                 分析:上述寫法限定參數(shù)在函數(shù)體中不可被改變。


            3.const 限定函數(shù)的值型返回值:

            const int Fun1();
            const MyClass Fun2();
                 分析:上述寫法限定函數(shù)的返回值不可被更新,當(dāng)函數(shù)返回內(nèi)部的類型時(shí)(如Fun1),已經(jīng)是一個(gè)數(shù)值,

            當(dāng)然不可被賦值更新,所以,此時(shí)const無意義,最好去掉,以免困惑。當(dāng)函數(shù)返回自定義的類型時(shí)(如Fun2)

            ,這個(gè)類型仍然包含可以被賦值的變量成員,所以,此時(shí)有意義。


            4. 傳遞與返回地址: 此種情況最為常見,由地址變量的特點(diǎn)可知,適當(dāng)使用const,意義昭然。


            5. const 限定類的成員函數(shù):

            class ClassName {
             public:
              int Fun() const;
             .....
            }
              注意:采用此種const 后置的形式是一種規(guī)定,亦為了不引起混淆。在此函數(shù)的聲明中和定義中均要使用

            const,因?yàn)閏onst已經(jīng)成為類型信息的一部分。

            獲得能力:可以操作常量對象。

            失去能力:不能修改類的數(shù)據(jù)成員,不能在函數(shù)中調(diào)用其他不是const的函數(shù)。

            三 inline 產(chǎn)生背景

            inline這個(gè)關(guān)鍵字的引入原因和const十分相似,inline 關(guān)鍵字用來定義一個(gè)類的內(nèi)聯(lián)函數(shù),引入它的主要原

            因是用它替代C中

            表達(dá)式形式的宏定義。

            表達(dá)式形式的宏定義一例:

               #define ExpressionName(Var1,Var2) (Var1+Var2)*(Var1-Var2)
                   這種表達(dá)式形式宏形式與作用跟函數(shù)類似,但它使用預(yù)編譯器,沒有堆棧,使用上比函數(shù)高效。但它只

            是預(yù)編譯器上符號表的簡單替換,不能進(jìn)行參數(shù)有效性檢測及使用C++類的成員訪問控制。

            inline 推出的目的,也正是為了取代這種表達(dá)式形式的宏定義,它消除了它的缺點(diǎn),同時(shí)又很好地繼承了它的

            優(yōu)點(diǎn)。inline代碼放入預(yù)編譯器符號表中,高效;它是個(gè)真正的函數(shù),調(diào)用時(shí)有嚴(yán)格的參數(shù)檢測;它也可作為

            類的成員函數(shù)。


            2) 具體作用

            直接在class類定義中定義各函數(shù)成員,系統(tǒng)將他們作為內(nèi)聯(lián)函數(shù)處理; 成員函數(shù)是內(nèi)聯(lián)函數(shù),意味著:每個(gè)

            對象都有該函數(shù)一份獨(dú)立的拷貝。
            在類外,如果使用關(guān)鍵字inline定義函數(shù)成員,則系統(tǒng)也會(huì)作為內(nèi)聯(lián)函數(shù)處理;

            C關(guān)鍵字
            #define 宏名
            要替換的代碼

            宏定義,保存在預(yù)編譯器的符號表中,執(zhí)行高效;作為一種簡單的符號替換,不進(jìn)行其中參數(shù)有效性的檢測


            typedef
            已有類型
            新類型

            別名,
            常用于創(chuàng)建平臺(tái)無關(guān)類型, typedef 在編譯時(shí)被解釋,因此讓編譯器來應(yīng)付超越預(yù)處理器能力的文本替換。

            posted @ 2009-02-15 10:32 王勇良 閱讀(302) | 評論 (0)編輯 收藏

            C 風(fēng)格(C-style)強(qiáng)制轉(zhuǎn)型如下:
            (T) exdivssion // cast exdivssion to be of type T
            函數(shù)風(fēng)格(Function-style)強(qiáng)制轉(zhuǎn)型使用這樣的語法:
            T(exdivssion) // cast exdivssion to be of type T

            這兩種形式之間沒有本質(zhì)上的不同,它純粹就是一個(gè)把括號放在哪的問題。我把這兩種形式稱為舊風(fēng)格(old-style)的強(qiáng)制轉(zhuǎn)型。


            使用標(biāo)準(zhǔn)C++的類型轉(zhuǎn)換符:static_cast、dynamic_cast、reinterdivt_cast、和const_cast。
            1. static_cast
            用法:static_cast < type-id > ( exdivssion )
            該運(yùn)算符把exdivssion轉(zhuǎn)換為type-id類型,但沒有運(yùn)行時(shí)類型檢查來保證轉(zhuǎn)換的安全性。它主要有如下幾種用法:
            ①用于類層次結(jié)構(gòu)中基類和子類之間指針或引用的轉(zhuǎn)換。
              進(jìn)行上行轉(zhuǎn)換(把子類的指針或引用轉(zhuǎn)換成基類表示)是安全的;
              進(jìn)行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成子類表示)時(shí),由于沒有動(dòng)態(tài)類型檢查,所以是不安全的。
            ②用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如把int轉(zhuǎn)換成char,把int轉(zhuǎn)換成enum。這種轉(zhuǎn)換的安全性也要開發(fā)人員來保證。
            ③把空指針轉(zhuǎn)換成目標(biāo)類型的空指針。
            ④把任何類型的表達(dá)式轉(zhuǎn)換成void類型。
            注意:static_cast不能轉(zhuǎn)換掉exdivssion的const、volitale、或者_(dá)_unaligned屬性。

            2. dynamic_cast
            用法:dynamic_cast < type-id > ( exdivssion )
            該運(yùn)算符把exdivssion轉(zhuǎn)換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void *;
            如果type-id是類指針類型,那么exdivssion也必須是一個(gè)指針,如果type-id是一個(gè)引用,那么exdivssion也必須是一個(gè)引用。
            dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。
            在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_cast和static_cast的效果是一樣的;
            在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast具有類型檢查的功能,比static_cast更安全。
            class B{
            public:
            int m_iNum;
            virtual void foo();
            };
            class D:public B{
            public:
            char *m_szName[100];
            };
            void func(B *pb){
            D *pd1 = static_cast(pb);
            D *pd2 = dynamic_cast(pb);
            }
            在上面的代碼段中,如果pb指向一個(gè)D類型的對象,pd1和pd2是一樣的,并且對這兩個(gè)指針執(zhí)行D類型的任何操作都是安全的;
            但是,如果pb指向的是一個(gè)B類型的對象,那么pd1將是一個(gè)指向該對象的指針,對它進(jìn)行D類型的操作將是不安全的(如訪問m_szName),
            而pd2將是一個(gè)空指針。
            另外要注意:B要有虛函數(shù),否則會(huì)編譯出錯(cuò);static_cast則沒有這個(gè)限制。
            這是由于運(yùn)行時(shí)類型檢查需要運(yùn)行時(shí)類型信息,而這個(gè)信息存儲(chǔ)在類的虛函數(shù)表(
            關(guān)于虛函數(shù)表的概念,詳細(xì)可見)中,只有定義了虛函數(shù)的類才有虛函數(shù)表,
            沒有定義虛函數(shù)的類是沒有虛函數(shù)表的。
            另外,dynamic_cast還支持交叉轉(zhuǎn)換(cross cast)。如下代碼所示。
            class A{
            public:
            int m_iNum;
            virtual void f(){}
            };
            class B:public A{
            };
            class D:public A{
            };
            void foo(){
            B *pb = new B;
            pb->m_iNum = 100;
            D *pd1 = static_cast(pb); //compile error
            D *pd2 = dynamic_cast(pb); //pd2 is NULL
            delete pb;
            }
            在函數(shù)foo中,使用static_cast進(jìn)行轉(zhuǎn)換是不被允許的,將在編譯時(shí)出錯(cuò);而使用 dynamic_cast的轉(zhuǎn)換則是允許的,結(jié)果是空指針。

            3. reindivter_cast
            用法:reindivter_cast (exdivssion)
            type-id必須是一個(gè)指針、引用、算術(shù)類型、函數(shù)指針或者成員指針。
            它可以把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以把一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)指針(先把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),
            在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值)。
            該運(yùn)算符的用法比較多。
            4. const_cast
            用法:const_cast (exdivssion)
            該運(yùn)算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和exdivssion的類型是一樣的。
            常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對象;
            常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對象;常量對象被轉(zhuǎn)換成非常量對象。
            Voiatile和const類試。舉如下一例:
            class B{
            public:
            int m_iNum;
            }
            void foo(){
            const B b1;
            b1.m_iNum = 100; //comile error
            B b2 = const_cast(b1);
            b2. m_iNum = 200; //fine
            }
            上面的代碼編譯時(shí)會(huì)報(bào)錯(cuò),因?yàn)閎1是一個(gè)常量對象,不能對它進(jìn)行改變;
            使用const_cast把它轉(zhuǎn)換成一個(gè)常量對象,就可以對它的數(shù)據(jù)成員任意改變。注意:b1和b2是兩個(gè)不同的對象。

            == ===========================================
            == dynamic_cast .vs. static_cast
            == ===========================================

            class B { ... };
            class D : public B { ... };

            void f(B* pb)
            {
            D* pd1 = dynamic_cast(pb);
            D* pd2 = static_cast(pb);
            }


            If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0.


            If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object.


            即dynamic_cast可用于繼承體系中的向下轉(zhuǎn)型,即將基類指針轉(zhuǎn)換為派生類指針,比static_cast更嚴(yán)格更安全。dynamic_cast在執(zhí)行效率上比static_cast要差一些,但static_cast在更寬上范圍內(nèi)可以完成映射,這種不加限制的映射伴隨著不安全性。static_cast覆蓋的變換類型除類層次的靜態(tài)導(dǎo)航以外,還包括無映射變換、窄化變換(這種變換會(huì)導(dǎo)致對象切片,丟失信息)、用VOID*的強(qiáng)制變換、隱式類型變換等...


            == ===========================================
            == static_cast .vs. reinterdivt_cast
            == ================================================


            reinterdivt_cast是為了映射到一個(gè)完全不同類型的意思,這個(gè)關(guān)鍵詞在我們需要把類型映射回原有類型時(shí)用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險(xiǎn)的。(這句話是C++編程思想中的原話)
            static_cast 和 reinterdivt_cast 操作符修改了操作數(shù)類型。它們不是互逆的; static_cast 在編譯時(shí)使用類型信息執(zhí)行轉(zhuǎn)換,在轉(zhuǎn)換執(zhí)行必要的檢測(諸如指針越界計(jì)算, 類型檢查). 其操作數(shù)相對是安全的。另一方面;reinterdivt_cast 僅僅是重新解釋了給出的對象的比特模型而沒有進(jìn)行二進(jìn)制轉(zhuǎn)換, 例子如下:


            int n=9; double d=static_cast < double > (n);


            上面的例子中, 我們將一個(gè)變量從 int 轉(zhuǎn)換到 double。 這些類型的二進(jìn)制表達(dá)式是不同的。 要將整數(shù) 9 轉(zhuǎn)換到 雙精度整數(shù) 9,static_cast 需要正確地為雙精度整數(shù) d 補(bǔ)足比特位。其結(jié)果為 9.0。而reinterdivt_cast 的行為卻不同:


            int n=9;
            double d=reinterdivt_cast (n);

            這次, 結(jié)果有所不同. 在進(jìn)行計(jì)算以后, d 包含無用值. 這是因?yàn)?reinterdivt_cast 僅僅是復(fù)制 n 的比特位到 d, 沒有進(jìn)行必要的分析.


            因此, 你需要謹(jǐn)慎使用 reinterdivt_cast. 


            posted @ 2009-02-15 10:30 王勇良 閱讀(305) | 評論 (0)編輯 收藏

            僅列出標(biāo)題
            共6頁: 1 2 3 4 5 6 
            国产精品成人久久久| 精品一区二区久久| 久久九九久精品国产免费直播| 久久w5ww成w人免费| 久久99精品久久久大学生| 一本色道久久88精品综合| 怡红院日本一道日本久久| 久久精品国产清高在天天线| 成人精品一区二区久久久| 99精品国产在热久久| 人妻无码αv中文字幕久久| 久久这里的只有是精品23| 久久久久亚洲av无码专区喷水| 国产午夜精品久久久久九九| 色综合久久久久无码专区 | 亚洲精品乱码久久久久久蜜桃| 久久亚洲国产中v天仙www | 99久久精品国产高清一区二区| 精品欧美一区二区三区久久久| 亚洲欧美日韩中文久久 | 伊人久久综合成人网| 国产精品无码久久久久| 精品久久久久久国产潘金莲 | 久久无码专区国产精品发布| 久久久久久综合一区中文字幕| 伊色综合久久之综合久久| 精品国产91久久久久久久a| 久久久久无码精品国产| 久久婷婷五月综合色奶水99啪 | 久久精品国产亚洲一区二区三区| 久久久久亚洲AV无码专区网站| 香蕉久久夜色精品国产2020| 夜夜亚洲天天久久| 伊人久久无码精品中文字幕| 国产激情久久久久影院小草 | 久久久久久免费视频| 久久久亚洲精品蜜桃臀| 亚洲国产天堂久久综合网站| 99久久婷婷国产一区二区| 欧美激情精品久久久久| 久久精品国产精品青草app|