Windows核心編程之核心總結(第二章 字元和字串處理)(2018.5.27)

來源:互聯網
上載者:User

標籤:Windows核心編程之核心總結

學習目標

第二章是學習字元和字串處理,為了更好理解這一章的內容,我自行添加了其他輔助性內容:儲存模式(大端儲存和小端儲存)、字元編碼方案(一看就懂)。以下是這一章的學習目標:
1.大端儲存和小端儲存
2.字元編碼方案
3.ANSI和Unicode字元、字串,Windows自訂資料類型(為了相容ANSI和Unicode)
4.Windows的ANSI函數和Unicode函數
5.C運行庫的ANSI和Unicode函數
6.C運行庫的安全字串函數
7.C運行庫的安全字串函數(進階版)
8.字串比較函數
9.寬字元和ASCII字元之間的轉換函式

必備知識-大端儲存和小端儲存

如何理解Big Endian(大端儲存)和Little Endian(小端儲存)?
舉個例子:
int a = 1;
a這個數本身的16進位表示是0x00 00 00 01
在記憶體中怎麼儲存呢?
如果你的CPU是intel x86架構的(基本上就是通常我們說的奔騰cpu),那麼就是0x01 0x00 0x00 0x00 , 這也就是所謂的little-endian, 低位元組存放在記憶體的低位.
如果你的CPU是老式AMD系列的(很老很老的那種,因為最新的AMD系列已經是x86架構了), 它的位元組序就是big-endian, 其記憶體儲存就是 0x00 0x00 0x00 0x01在記憶體中從高位元組開始存放。
現在世界上絕大多數的CPU都是little-endian。

字元編碼

發展流程:ASCII-擴充ASCII-GB2312-GBK-GB18030
美國是最先開始使用電腦,一個位元組有八位二進位,能夠組合出256種不同的狀態,他們將控制字元、空格、標點符號、數字、大小寫字母分別用連續的位元組狀態表示,一直編碼到了第127號,這樣電腦就可以用不同位元組來儲存英語文字了。這時,他們這個方案就叫做ANSI的ASCII編碼。後來電腦普及到了世界,因為127種不能表示其他國家的字元、文字,接著他們就想擴充ASCII,使用127後面的位元來其他的字元和文字,一直編碼到狀態255,從126到255這一頁的字元集稱作擴充字元集。等到我們中國使用電腦,那麼僅僅依靠ASCII完全不夠儲存,中國就自己想法子解決這一問題,我們這樣規定:一個小於127的字元意義還是與原來的ASCII編碼相同,但兩個大於127的字元連在一起時,就表示一個漢字。意思是說:一個英文字母還是一個位元組儲存,而一個漢字用兩個位元組來表示,低8位元組儲存在低地址位置,高8位元組儲存在高地址位置,如果在低地址位置儲存的低8位元組的第8個二進位位大於1,在高地址位置儲存的高8位元組的第8個二進位位也大於1,那麼就會被識別為一個漢字。這樣我們大概就可以組合出大約7000多個簡體漢字了,在這些編碼種,我們還將數學符號、連ASCII原本就有的數字、標點、字母都統統重新編碼了,這就是我們遇到的“全形”字元,而原來在127號以下的就叫“半形”字元。之後,中國就叫這個漢字編碼方案稱為“GB2312”。後來,中國文化博大精深,有些漢字還沒完全編碼,所以就決定乾脆只要高位元組代表的字元大於127就表示該字元為漢字,然後稱這個編碼方案為 GBK 標準。當用GBK解碼時,若高位元組最高位為0,則用ASCII編碼碼解碼;若高位元組最高位為1,則用GBK編碼錶解碼。GBK之後又有GB18030標準,因GB18030較GBK又多了幾千漢字,碼位不足,GB18030使用了2byte與4byte混合編碼方式,這又給軟體增加了難題,所以雖然GB18030推出了近5年,仍然沒有得到廣泛應用。前面講的一堆漢字編碼,我們總稱為“DBCS”,即雙位元組字元集。經過前面編碼的發展,逐漸出現一個嚴重的問題,就是當時各個國家都像中國這樣搞出一套自己的編碼通訊協定,結果互相之間誰的電腦都不認識對方,誰也不支援對方的編碼。後來,先輩們就想到,廢除所有的地區性編碼方案,重新搞一個包括了地球上所有文化、文字和符號的編碼方案,他們稱這個編碼方案為“Unicode編碼”。
Unicode編碼有以下幾種:
UTF-8:一個位元組一個字元,有些字元是2個位元組,有的字元是3個位元組,還有的字元是4個位元組。
UTF-16:大部分字元都是2個位元組。Windows平台下預設的Unicode編碼為Little Endian的UTF-16。
UTF-32:所有字元都是4個位元組。

ANSI和Unicode字元、字串,Windows自訂資料類型

ANSI字元就是C語言用char資料類型代表一個8位的字元。ANSI字串是多個char資料類型組成的數組,代表多個位元組的字串。例如:

char a=‘a‘;//‘a‘這個常量字元在常量儲存區儲存為1個位元組。而a在棧區儲存為1個位元組。char szBuffer[10]="abcdefg";//"abcdefg"這個常量字元在常量儲存區儲存為8個位元組。而szBuffer在棧區儲存為10個位元組。

在以前,Unicode字元用wchar_t代表一個兩位元組的寬字元(Unicode字元),以前C標頭檔有這樣的定義:typedef unsigned short wchar_t,說明wchar_t其實也只是一個無符號短整型而已。後來C編譯器將wchar_t定義為與Int一樣是基礎資料型別 (Elementary Data Type),這時候,在高版本一點的編譯器你是找不到typedef unsigned short wchar_t這條語句的了。如果想表示常量字元和常量字串為Unicode版本,那麼就要在前面加個L。例如:

wchar_t c=L‘a‘;//L‘a’這個常量字元在常量儲存區儲存為2個位元組。而c在棧區儲存為2個位元組。wchar_t szBuffer[10]=L"abcdefg";//L"abcdefg"這個常量字元在常量儲存區儲存為16個位元組。而szBuffer在棧區儲存為20個位元組。

為了與C語言稍微一些區分,並且為了相容ANSI和Unicode字元或字串,Windows自訂了一些資料類型:TCHAR資料類型、TEXT宏。
而對於TCHAR資料類型和TEXT宏的標頭檔定義如下:

#ifdef  UNICODE                     // r_winnt#ifndef _TCHAR_DEFINEDtypedef WCHAR TCHAR, *PTCHAR;typedef WCHAR TBYTE , *PTBYTE ;#define _TCHAR_DEFINED#endif /* !_TCHAR_DEFINED */typedef LPWCH LPTCH, PTCH;typedef LPCWCH LPCTCH, PCTCH;typedef LPWSTR PTSTR, LPTSTR;typedef LPCWSTR PCTSTR, LPCTSTR;typedef LPUWSTR PUTSTR, LPUTSTR;typedef LPCUWSTR PCUTSTR, LPCUTSTR;typedef LPWSTR LP;typedef PZZWSTR PZZTSTR;typedef PCZZWSTR PCZZTSTR;typedef PUZZWSTR PUZZTSTR;typedef PCUZZWSTR PCUZZTSTR;typedef PZPWSTR PZPTSTR;typedef PNZWCH PNZTCH;typedef PCNZWCH PCNZTCH;typedef PUNZWCH PUNZTCH;typedef PCUNZWCH PCUNZTCH;#define __TEXT(quote) L##quote      // r_winnt#else   /* UNICODE */               // r_winnt#ifndef _TCHAR_DEFINEDtypedef char TCHAR, *PTCHAR;typedef unsigned char TBYTE , *PTBYTE ;#define _TCHAR_DEFINED#endif /* !_TCHAR_DEFINED */typedef LPCH LPTCH, PTCH;typedef LPCCH LPCTCH, PCTCH;typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR;typedef PZZSTR PZZTSTR, PUZZTSTR;typedef PCZZSTR PCZZTSTR, PCUZZTSTR;typedef PZPSTR PZPTSTR;typedef PNZCH PNZTCH, PUNZTCH;typedef PCNZCH PCNZTCH, PCUNZTCH;#define __TEXT(quote) quote         // r_winnt#endif /* UNICODE */                // r_winnt#define TEXT(quote) __TEXT(quote) 

從標頭檔定義中,我們可以看出TCHAR資料類型有兩種可能,如果定義了UNICODE,則是WCHAR(其實就是wchar_t,寬字元),如果定義了非UNICODE(多位元組字元集),則是char(窄字元)。我們知道,當我們開啟vs編譯器,預設採取的是Unicode字元集,其實這個選項代表我們寫的程式序加了這句代碼:#define UNICODE。那說明我們寫TCHAR,其實就是wchar_t。而如果我們在選項中更改字元集為多位元組字元集,那麼就相當於定義了非UNICODE,那說明我們寫TCHAR,其實就是char。而對於TEXT宏也同樣道理,如果是UNICODE字元集,那麼就轉定義為L##quote(代表在quote前面添加L,quote可以是字元,也可以是字串),如果是多位元組字元集,那麼就轉定義為quote(代表什麼都不添加)。下面舉個例子:

//Unicode字元集TCHAR c=TEXT(‘a‘);//TEXT(‘a‘)相當於L’a‘,在常量儲存區儲存為2個位元組。而c在棧區儲存為2個位元組。TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相當於L"abcdefg",在常量儲存區儲存為16個位元組。而szBuffer在棧區儲存為20個位元組。
//多位元組字元集TCHAR c=TEXT(‘a‘);//TEXT(‘a‘)相當於’a‘,在常量儲存區儲存為1個位元組。而c在棧區儲存為1個位元組。TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相當於"abcdefg",在常量儲存區儲存為8個位元組。而szBuffer在棧區儲存為10個位元組。

Windows是不是很智能?相容了ANSI和Unicode,通過TCHAR和TEXT宏可以自動採用對應編碼方式進行編碼。

Windows的ANSI函數和Unicode函數
  1. 在windows中,有UNICODE類型的函數和ASCII類型的函數,例如CreateWindowEx函數。
    在WinUser.h中,有如下定義:

    #ifdef UNICODE#define CreateWindowEx  CreateWindowExW#else#define CreateWindowEx  CreateWindowExA#endif // !UNICODE

    根據上面標頭檔,我們就知道了,CreateWindowExW函數是支援Unicode字元的,而CreateWindowExA是支援ANSI字元的。原來Windows函數也會考慮到ANSI和Unicode字串問題,所以為了相容這兩者,就歸為CreateWindowEx函數了,會自動根據情況自行選擇正確的函數。其實,還有一個內部原理:CreateWindowExA函數內部實現的其實只是一個轉換層,它負責分配記憶體,以便將ANSI字串轉換為Unicode字串,然後內部代碼會調用CreateWindowExW,並向它傳遞轉換後的字串,CreateWindowExW返回時,CreateWindowExA會釋放它的記憶體緩衝區,並返回視窗控制代碼。這個內部原理,總結一句話就是雖然我們調用的是CreateWindowExA,但實際函數內部是先將ANSI字串轉換為Unicode字串,再調用CreateWindowExW,最後釋放記憶體,接著CreateWindowExA返回內部調用的CreateWindowExW返回的視窗控制代碼。

    C運行庫的ANSI和Unicode函數

    C運行庫提供了一些字串操作函數來處理ANSI字元和Unicode字元。例如:strlen和wcslen函數,分別支援ANSI字串和Unicode字串。

    //字元集為Unicode字元集char szBuffer1[5]="abcd";printf("%d\n", strlen(szBuffer1));TCHAR szBuffer2[5] = TEXT("abcd");printf("%d\n", wcslen(szBuffer2));

    而C運行庫為了能智能相容ANSI和Unicode,提供了_tcslen函數,這個函數需要標頭檔tchar.h,並且定義了_UNICODE。
    tchar.h標頭檔定義了以下宏:

    #ifdef _UNICODE#define _tcslen wcslen#else#define _tcslen strlen#endif

    如果包含了標頭檔tchar.h,並且字元集設定為Unicode字元集,那麼就已經定義了_UNICODE,我也不知道為什麼設定Unicode字元集就會自動定義_UNICODE,然後就可以直接使用_tcslen,也許是因為設定字元集這個操作內部就有#define _UNICODE這行代碼吧。下面舉個例子:

    //已經設定字元集為Unicode字元集了#include<windows.h>#include<tchar.h>int main(){TCHAR szBuffer3[5] = TEXT("abcd");printf("%d\n", _tcslen(szBuffer3));system("pause");return 0;}
    C運行庫的安全字串函數

    我們編程的時候,盡量使用安全字串,例如strcpy就是一個非安全函數,當你再程式中使用這個函數的時候,你就會發現,編譯器會出現警告,同時給出建議,請遵守。編譯器會提示我們使用strcpy_s函數,此時,我們可以尋找這個函數,並找到這個函數的TCHAR.h版。具體使用方法不是很難,你要使用那個字串,就在MSDN中找相應的安全字串函數即可。不過,對於strlen、wcslen和_tcslen等函數沒有問題,可以放心使用,因為它們不會修改傳入的字串。

    C運行庫的安全字串函數(進階版)

    C運行庫還新增了一些函數,用於在執行字串處理時提供更多的控制。例如:StringCchLength、StringCchPrintf等函數,更多函數請參考MSDN。
    下面是StringCchPrintf函數的說明:
    StringCchPrintf函數用於把格式化字串寫入指定的緩衝區中,與wsprintf函數不同之處在於,該函數還另外需要提供目標緩衝區的大小,確保不會發生越界訪問。(因為wsprintf函數,若緩衝區大小不足以儲存格式化字串,則不允許寫入,而且會發生崩潰。但是StringCchPrintf函數指定了目標緩衝區大小,意味著,緩衝區大小不足以儲存格式化字串,也可以截斷,只儲存參數1(緩衝區大小)的長度的字串),這樣就避免發生了奔潰。標頭檔 strsafe.h。

函數原型:
HRESULT StringCchPrintf(
Out LPTSTR pszDest,
In size_t cchDest,
In LPCTSTR pszFormat,
In ...
);

參數1:指定將要被寫入的緩衝區
參數2:限制緩衝區大小
參數3:格式化字串
參數4:可變參數

    TCHAR szBuffer[10];    wsprintf(szBuffer, TEXT("%s"), TEXT("woainiaifbgfbfgbfgbgf"));//當目標緩衝區不夠儲存源緩衝區內容,則會溢出崩潰    StringCchPrintf(szBuffer, 10, TEXT("%s"),TEXT("wwoainiaifbgfbfgbfgbgf"));//新的安全字串函數增加了一個緩衝區大小參數,如果超過目標緩衝區大小則會自動截斷,避免了溢出崩潰

下面是StringCchLength函數的說明:
StringCchLength函數用於確定字串是否超過了規定長度。與lstrlen函數的區別在於,該函數指定了待檢查的字串的最大允許的字元數量。注意,如果待檢查的字串(雙引號)長度大於最大允許的字元數量,參數3置為0。如果待檢查的字串(單引號),沒有字串結束符,則無論設定cchMax為多少,都會置參數3為0.

*函數原型:
HRESULT StringCchLength(
In LPCTSTR psz,
In size_t cchMax,
Out size_t
pcch
);
參數1:指向待檢查的字串
參數2:psz參數裡最大允許的字元數量。
參數3:字串的字元數,不包括‘\0‘**

    size_t iTarget1,iTarget2,iTarget3;    TCHAR szBuffer1[10] =TEXT("但是我依然很開心呀");    StringCchLength(szBuffer1, 5, &iTarget1);//如果待檢查的字串(雙引號)長度大於最大允許的字元數量,參數3置為0。不會報錯。    TCHAR szBuffer2[3] = { L‘a‘, L‘b‘, L‘c‘ };    StringCchLength(szBuffer2, 5, &iTarget2);//如果待檢查的字串(單引號),沒有字串結束符,則無論設定cchMax為多少,都會置參數3為0.不會報錯。    TCHAR szBuffer3[10] = TEXT("但是我依然很開心呀");    StringCchLength(szBuffer3, 10, &iTarget3);//成功了

總結,StringCch*系列的函數是安全的,因為可以指定如何截斷,不會發生崩潰。

字串比較函數
int CompareString(  __in  LCID Locale,  __in  DWORD dwCmpFlags,  __in  LPCTSTR lpString1,  __in  int cchCount1,  __in  LPCTSTR lpString2,  __in  int cchCount2);
int CompareStringOrdinal(  __in  LPCWSTR lpString1,  __in  int cchCount1,  __in  LPCWSTR lpString2,  __in  int cchCount2,  __in  BOOL bIgnoreCase);

CompareStringOrdina和語言無關,速度更快!!!建議使用!!!因為字串操作函數在實際應用中可以查詢MSDN,我以後再填補回來。

寬字元和ASCII字元之間的轉換函式
int MultiByteToWideChar(  __in   UINT CodePage,  __in   DWORD dwFlags,  __in   LPCSTR lpMultiByteStr,  __in   int cbMultiByte,  __out  LPWSTR lpWideCharStr,  __in   int cchWideChar);
int WideCharToMultiByte(  __in   UINT CodePage,  __in   DWORD dwFlags,  __in   LPCWSTR lpWideCharStr,  __in   int cchWideChar,  __out  LPSTR lpMultiByteStr,  __in   int cbMultiByte,  __in   LPCSTR lpDefaultChar,  __out  LPBOOL lpUsedDefaultChar);

因為字串操作函數在實際應用中可以查詢MSDN,我以後再填補回來。

Windows核心編程之核心總結(第二章 字元和字串處理)(2018.5.27)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.