上一篇咱們學習了Windows編程的文本及字體輸出,在以上幾篇的實例中也出現了一些帶有「TEXT」的Windows宏定義,有朋友留言想了解一些ANSI和Unicode編程方面的內容,本章就來了解和學習一些Windows下關於ANSI和Unicode方面的編程基礎。java
計算機最先在美國誕生,因此最開始都是以英語爲做爲交互語言,因爲只有26個字母,用一個字節(範圍-128 ~ 127)表示,這個範圍足夠表示26個由於字符和一些經常使用的控制字符,這個就是ASCII編碼。所以最先的各類程序設計語言以及使用的字符串都用字節數組表示,也確實知足了編程的各類需求。可是隨着計算機的普及,範圍上逐漸超出了英語使用的國家,這樣一來,字符編碼就成了問題,由於不少國家的語言字符數目根本不能用一個字節來表示,好比咱們國家的中文,經常使用的就有四千多個,若是再加上一些不經常使用的字符,更是遠遠不止這些,所以一個字節的字符串編碼就行不通了,那麼天然而然就出現了兩個字節甚至跟多字節的編碼方式了。程序員
除了基本的ASCII編碼外,目前經常使用的字符編碼有MBCS、BG23十二、GBK、UTF-八、UTF-1六、 UTF-3二、BIG五、Base6四、Unicode等等,其實Unicode就是使用UTF-16編碼。如今的全部系統都支持多字節編碼,Windows98之前的對Unicode支持很差,不少內核函數都須要將字符串轉換以後才能處理,從Windows NT系統後幾乎都採用了Unicode編碼從新系統內核,非Unicode的編碼會通過轉換以後在傳入內核處理。編程
在C語言誕生的時候,一樣尚未遇到多字節字符串問題,固然也沒有Unicode等這些編碼,標準的C語言庫函數處理字符串時都是ASCII編碼,所以用標C函數處理多字節字符編碼就存在問題,因此不一樣系統都在內部進行這種字符編碼的處理。那麼問題來了,既然標C不支持Unicode,咱們又如何編程使用Unicode呢?咱們如何指定程序中的字符串採用ASCII仍是Unicode或者兩種同時出如今一個程序裏面呢? 更好的狀況,咱們如何編寫程序,根據本身的需求編譯ASCII和Unicode(如下稱寬字符)版本?本文咱們就來談談這個問題。在微軟公司提供的C/C++編譯器中提供了一個wchar_t的變量類型,這個類型其實是經過typedef定義的一個無符號16位整型數。咱們使用這個來定義寬字符版本的字符和字符串,而普通的ANSI仍是標準C語言的char來定義。小程序
寬字符串的使用windows
下面咱們對比一下ASCII和Unicode字符(串)的定義及常量的定義方式。數組
ASCII版本:微信
Char c = ‘A’; Char str[] = 「hello, world」;
寬字符版本:微信公衆平臺
wchar_t wch = L’A’; wchar_t wstr[] = L「hello, world」;
微軟的編譯器經過這個大寫字母「L」開頭來識別後面的字符串將編譯爲一個Unicode的字符或字符串,注意這裏的L後面不能有空格。ide
看下面的實例:函數
#include <windows.h> #include <stdio.h> int main(void) { char c = 'A'; char str[] = "hello, ANSI"; wchar_t wch = L'A'; wchar_t wstr[] = L"hello, Unicode"; printf("1 --> %c\n", c); printf("2 --> %s\n", str); printf("3 --> %c\n", wch); printf("4 --> %s\n", wstr); printf("5 --> %C\n", c); printf("6 --> %S\n", wstr); wprintf(L"7 --> %c\n", wch); wprintf(L"8 --> %s\n\n", wstr); system("pause"); return 0; }
這個小程序的輸出以下:
能夠看出:
用printf能夠輸出ANSI的字符和字符串(廢話)
用wprintf能夠輸出Unicode字符和字符串
printf能夠用大寫的字母C、S,即「%C」「%S」來輸出寬字符和字符串
能夠看出第3和第4用printf能夠輸出寬字符,但寬字符串僅僅輸出了字符串的第一個字符,實際上這個就是問題了,不能這樣輸出,第3的字符A實際上徹底是運氣好,由於Unicode是雙字節,因此寬字符」A」實際在是十六進制的「00 41」,而Windows系統是一個小端系統,因此在內存中的排版爲「41 00 ……」,因此第一個恰好輸出A。而第4只能輸出一個「h」,也是由於這個緣由。字符串wstr在內存的存在形式以下如:
第一個字符是「h」,它的寬字符在內存排布(小端系統)爲」68 00 …」,根據C語言規則,字符串以空字符0x00爲結束符,所以使用printf和%s來輸出時,系統並不知道這個h是一個寬字符,而是以此向後一直到空字符,這裏恰好第二個就碰上了,所以只能輸出一個「h」。
一樣,scanf函數也是如此:
scanf("%s", str); //這個是C語言的正經常使用法
scanf("%s", wstr); //這個是能夠工做的,可是接收結果是ANSI格式的字符串
scanf("%S", wstr); //這個能夠正確接收寬字符格式的字符串
wscanf(L"%s", wstr); //這個是標準的接收寬字符格式字符串
以上的printf和scanf用%S來處理寬字符的方式是微軟擴展的,不必定其餘編譯系統也能這樣處理。
Unicode字符串支持函數
從上面咱們看出,微軟的編譯器對寬字符及寬字符串常量用一個大寫的「L」做爲前綴來高手編譯,後面的字符串做爲Unicode版本而不是ANSI版本。另外printf和scanf也有對於的寬字符版本函數wprintf和wscanf來處理,從MSDN咱們知道,全部關於字符/字符串都有兩個版本,好比_wfopen、_getws、wcslen、wcscpy、wcscat等就是標準C函數fopen、gets、strlen、strcpy、strcat的寬字符版本。除了這些標C的寬字符函數外,Windows的API一樣有ANSI和Unicode版本,好比建立窗體和空間的CreateWindowA、CreateProcessA等就是ANSI版本,而對應的CreateWindowW、CreateProcessW就是Unicode版本,他們處理的字符串類型都必須是wchar_t的字符串。
在一個程序裏面,咱們可使用ANSI版本的函數來處理對應的字符串,同時也可使用Unicode版本的函數來處理wchar_t的字符串,正如上面的實例同樣,但必須對應,不然可能出現編譯錯誤,更麻煩的是有可能編譯經過可是結果卻不是咱們想要的,如上面的第4一條輸出。
固然若是不是須要,最好不要在程序裏面一下子使用ANSI,一會使用Unicode,這樣對未來的移植性兼容性不好,也不利於多語種和國際化。強烈建議使用Unicode版原本編寫程序,這個是一個大趨勢,若是你要把PC平臺的Windows程序移植到微軟的嵌入式平臺Win CE上的話,就必須是Unicode。微軟爲了簡化和通用性,在Win CE平臺上只支持Unicode。並且使用Unicode編碼時運行效率更高,由於如今的Windows操做系統內核所有都是用Unicode版本,若是上面傳入一個ANSI的,它必須先轉換成Unicode字符串,再傳入內部的函數處理。
同時支持兩種編碼
固然理想狀況是若是編寫統一的應用程序,在編譯時想編譯成ANSI就編譯成ANSI版本,想編譯成Unicode版本就編譯成Unicode版本是最好的,這樣咱們寫出來的程序不論是移植性仍是通用性都最好,其實這個微軟早就想到了。
微軟針對標準C函數構造了一套平臺相關的字符串處理宏定義,所謂平臺相關就是說這些宏是微軟本身定義的,只是在Windows平臺下使用,不是標準裏面的東西。這些定義在不一樣的狀況下會變成不一樣的版本。若是定義了「_UNICODE」這個宏定義,Windows將在處理C/C++函數是採用Unicode版本,不然就是ANSI版本。下面咱們以strlen這個函數來看一下Windows是怎麼定義的:
#ifdef _UNICODE #define _tcslen wcslen #else #define _tcslen strlen #endif
這裏的_tcslen就是那個平臺相關的求字符串的字符長度的宏定義,固然咱們在使用的時候把他當作函數就好了,能夠看到若是定義了_UNICODE,那麼_tcslen在編譯時實際是連接的wcslen,不然連接strlen。如今咱們打開VS下面的頭文件「tchar.h」,就能夠看到不少如下劃線開頭的宏定義,這些都是平臺相關的通用字符串處理庫函數:
因此使用這些函數的時候要包含這個頭文件。
另外,若是定義了「UNCODE」這個宏,Windows的API也會採用Unicode版本,不然採用ANSI版本。好比CreateWindow這個函數定義以下:
#ifdef UNICODE #define CreateWindow CreateWindowW #else #define CreateWindow CreateWindowA #endif // !UNICODE
因此實際上CreateWindow是一個宏定義而已,可是這不影響咱們把它當作函數來使用,一樣其餘含有字符串做爲參數的Windows API也一樣作了定義。
默認狀況下,咱們使用VS來創建工程,_UNICODE和UNICODE這兩個宏都是打開的,因此咱們用嚮導建立的工程都是Unicode版本的,咱們也能夠從配置選項裏面刪除這兩個定義來編譯ANSI版本的程序。
如今函數的使用解決了,那麼如何來定義字符以及字符串的變量類型已常常量,使得_UNICODE和UNICODE定義也能影響類型和常量呢?微軟一樣使用了一系列的定義來解決這個問題。TCHAR是做爲字符、字符串的變量類型,等價於char和wchar_t,若是定義了UNICDOE,TCHAR其實是wchar_t,不然就是char,這個在winnt.h中能找到。
對字符串常量,VS定義了TEXT、__TEXT,在tchar.h中,還定義了_T等好幾種方式,只要定義了UNICODE,則這些宏定義就是Unicode,不然就是ANSI版本。所以咱們之後在編寫程序時,應該充分用這些宏來定義字符串類型變量,常量以及處理函數。下面是一個推薦的簡單實例:
#include <windows.h> #include <tchar.h> int _tmain(void) { TCHAR c = TEXT('A'); TCHAR buf[16]; TCHAR *str = TEXT("hello, world!"); _tprintf(TEXT("1 --> %c\n"), c); _tprintf(TEXT("2 --> %s\n"), str); _tscanf(_T("%s"), buf); _tprintf(_T("%s\n"), buf); _tsystem(TEXT("pause")); return 0; }
在這個實例中,全部可能用到字符串的函數都採用通用的函數,能正確的編譯Unicode版本和ANSI版本。
Unicode和ANSI字符串轉換
有時候咱們可能仍是會出現不一樣編碼之間的轉換,這是咱們能夠採用Windows提供的API來完成。
MultiByteToWideChar函數和WideCharToMultiByte函數,這兩個函數能夠在ANSI和Unicode字符串之間來回轉換。他們的參數有不少類似之處,原型爲:
int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar); int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
具體用法能夠參考MSDN,網上也能找到大量的使用說明和實例,這裏就再也不敘述。
下面給一個實例來演示ANSI和Unicode之間的轉換:
#include <windows.h> #include <tchar.h> #include <stdio.h> int _tmain(void) { int nwCh; char AnsiStr[] = "hello, world!"; wchar_t wszBuf[20] = {0}; //得到轉換後產生多少Unicode字符,能夠做爲後面實際轉換時傳入容納轉換結果的Unicode字符數buffer大小 nwCh = MultiByteToWideChar(CP_ACP, 0, AnsiStr, -1, NULL, 0); //轉換並接收結果 MultiByteToWideChar(CP_ACP, 0, AnsiStr, -1, wszBuf, nwCh); wprintf(L"nwCh = %d, %s\n", nwCh, wszBuf); int nCh; char AnsiBuf[20] = {0}; //得到轉換後產生多少ANSI字符,能夠做爲後面實際轉換時傳入容納轉換結果的ANSI字符數buffer大小 nCh = WideCharToMultiByte(CP_ACP, 0, wszBuf, -1, NULL, 0, NULL, NULL); //轉換並接收結果 WideCharToMultiByte(CP_ACP, 0, wszBuf, -1, AnsiBuf, nCh, NULL, NULL); printf("nCh = %d, %s\n", nCh, AnsiBuf); _tsystem(TEXT("pause")); return 0; }
請注意註釋部分,該函數及能夠轉換,也能獲取轉後所需輸出的存儲字符個數空間的大小。運行後的輸出結果:
到這裏本文就結束了,下一篇將繼續咱們的Windows編程系列之旅。敬請關注!
更多經驗交流能夠加入Windows編程討論QQ羣:454398517。
關注微信公衆平臺:程序員互動聯盟(coder_online),你能夠第一時間獲取原創技術文章,和(java/C/C++/Android/Windows/Linux)技術大牛作朋友,在線交流編程經驗,獲取編程基礎知識,解決編程問題。程序員互動聯盟,開發人員本身的家。
轉載請註明出處,謝謝合做!