一、回顧C語言中的char數據類型
1>. 在C語言中, 首先我們來聲明一個字符型變量:
char c ;
我們也可以在聲明時對其進行初始化:
char c = 'A' ;
這時, 字符型變量c就會被用值0x41進行初始化, 0x41也就是ASCII碼中的'A'字符;
2>. 我們還可以定義一個指向一個字符型數據的指針, 例如:
char *p ;
同樣我們再讓指針p初始化時指向一個字符串:
char *p = "Hello,world!" ;
3>. 我們再聲明一個字符數組:
char a[10] ;
也可以在聲明時對其進行初始化:
char a[10] = "Hello" ;
或者:
char a[] = "Hello" ;
通過對C語言的學習我們可以知道, char型變量為1個字節, 因此, 在char c = 'A' ;中, 變量c的大小即為1字節; 在32位的操作系統中, 一個指針型的變量需要4字節的存儲空間, 因此在第二個示例中, char *p = "Hello,world!" ;所需的存儲空間為: 4字節指針變量所需的空間 + 字符串"Hello,world!"的12個字節另外再加上一個字節用來表示字符串結束的0。
對於一個字符數組char a[10] ;編譯器則會自動保留10個字節的儲存空間, 對於char a[] = "Hello" ;這種聲明方式, 編譯器會根據"Hello"字符串的長度( 5個字符 + 一個結尾0 )來決定初始化時的數組大小。
二、寬字符
通過上午學習的Unicode編碼方式中可以知道, 一個Unicode字符占用2個字節的儲存空間, 如果我們想用C語言中原有的數據類型表示Unicode的2字節編碼類型, char是不行的, char的儲存空間為一個字節, 在32位的環境下, int 占4個字節, 而我們僅僅需要的是2字節, 並且是無符號型數據, 因此, 我們可以使用unsigned short int型數據表示一個2字節的字符, unsigned short int為2字節, 正好符合2字節的要求, 當然, 我們也可以將unsigned short int簡寫為unsigned short。
在C語言中的寬字符正是基於short型數據的, 這一數據類型在頭文件WCHAR.H中的定義為:
typedef unsigned short wchar_t ;
所以C語言中的寬字符wchar_t數據類型與一個無符號短整形unsigned short一樣, 都是16位寬。
如果我們用寬字符wchar_t數據類型定義一個變量並且初始化, 如下:
wchar_t c = 'A' ;
那么寬字符wchar_t變量c的值為0x0041, 學過匯編的朋友應該知道, 如果使用16位的CPU儲存一個字, 將使用兩個存儲單元, 在這兩個存儲單元中, 低位字節放在低地址單元中, 高位字節則放在高地址單元中, 所以, 在這里, 處理器依然將從低位內存單元即低位字節開始處理字符, 'A'在內存中的順序即為 0x41, 0x00。
當我們想使用寬字符表示一個字符串, 我們還要通知編譯器這個字符串將使用寬字符存儲, 我們用大寫的字母'L'(表示長整形)來將這一消息告訴編譯器, 例如:
wchar_t *p = L"Hello" ;
那么這個字符串"Hello"將會使用12個字節來儲存, 這12個字節存儲單元的內容為:"Hello"這5個字符占10個字節, 另外加上表示結束的0占兩個字節。
三、有關寬字符的函數
在C語言的學習中, 字符串處理函數我們經常使用, 比如:
unsigned int strlen(char *s); //求字符串的長度 char *strcat( char *dest, char *src ) ; //將src所指字符串連接到dest結尾處 int strcmp( char *s1, char * s2 ) ; //將字符串s1與s2比較 char *strcpy( char *dest, char *src ) ; //將src所指字符串覆蓋方式復制到dest中 ...
這些函數極大的方便了我們對字符串的處理, 遺憾的是, 在寬字符類型的字符串中, 這些函數將不再適用, 例如我們使用strlen求一個寬字符字符串的長度, 代碼如下:
#include <stdio.h> #include <string.h> int main() { wchar_t *p = L"Hello" ; printf( "%d\n", strlen(p) ) ;
return 0 ; }
運行后顯示的結果為1, 很顯然, 它沒有求出正確的長度, 這是因為, 寬字符字符串"Hello"在一段內存中存儲的值如下:
48 00 65 00 6C 00 6C 00 6F 00 21 00
這是因為當strlen找到該字符串的第一個0時就認為該字符串已經結束了, 所以得到的長度為1, strlen統計到的這一個字符即為0x48表示的'H'。
幸運的是, 雖然這些字符串處理函數不支持對寬字符的處理, 但是我們可以使用為寬字符處理准備的函數, C語言中每個字符串處理函數對應的都有其寬字符版本的字符串處理函數, 這些函數定義在STRING.H頭文件和WCHAR.H中, 例如strlen函數響應的寬字符版本為wcslen, wcslen函數在STRING.H的聲明如下:
size_t wcslen(const wchar_t *);
size_t為無符號整形unsigned int的別名, STRING.H在頭文件的定義如下:
typedef unsigned int size_t;
我們嘗試使用寬字符處理函數wcslen()求寬字符字符串的長度:
#include <stdio.h> #include <string.h> int main() { wchar_t *p = L"Hello" ; printf( "%d\n", wcslen( p ) ) ;
return 0 ; }
編譯運行后的結果顯示為5, 是正確的, 同樣, 在寬字符中常用的字符串處理函數如下:
| 函數名 |
函數原型 |
函數功能 |
返回值 |
| wcscat |
wchar_t *wcscat(wchar_t *s1, const wchar_t *s2); |
將s2所指的字符串連接到s1后面 |
s1所指字符串的首地址 |
| wcschr |
wchar_t *wcschr(const wchar_t *s, wchar_t c); |
在s字符串中找到c字符第一次出現的位置 |
若找到, 則返回該字符的地址, 否則返回NULL |
| wcscmp |
int wcscmp(const wchar_t *s1, const wchar_t *s2); |
讓字符串s1與字符串s2進行比較 |
s1 < s2, 返回負數; s1 == s2, 返回0;s1 > s2, 返回正數 |
| wcscpy |
wchar_t *wcscpy(wchar_t *s1, const wchar_t *s2); |
將s2所指字符串覆蓋方式復制到s1中 |
s1所指的字符串的首地址 |
| wcslen |
size_t wcslen(const wchar_t *s); |
求s所指字符串的長度 |
返回有效字符的個數 |
| wcsstr |
wchar_t * wcsstr(const wchar_t *s1, const wchar_t *s2); |
找出字符串s2在字符串s1中第一次出現的位置 |
若找到, 則返回該位置的地址, 否則返回NULL |
四、遺留問題:關於TEXT()
昨天在學習過程中有個遺留問題:
在MessageBox對話框中, MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );使用TEXT()的作用是什么呢?
在今天的學習中終於找到了答案。
在定義wchar_t寬字符類型的字符串時, 我們需要在字符串前面加上個大寫字母'L'來告訴編譯器這是一個wchar_t寬字符型的字符串, 而TEXT()正是為了解決在定義如何不加前面的'L'的。
當我們使用寬字符時, 打開TCHAR.H這個頭文件向下查找, 會看到:
#define __T(x) L ## x
這句宏定義, 在ANSI C標准的預處理中, 符號"##"被解釋為"令牌粘貼", 作用是使得字母'L'與宏參數連接在一起, 假如參數x為"Hello", 那么經過L ## x處理后"Hello"就變成了L"Hello"。
這看起來和TEXT()沒什么關系, 繼續向下查找'__T(x)'會看到:
#define _T(x) __T(x) #define _TEXT(x) __T(x)
這兩行宏定義, 到這里仍然沒有看到TEXT(), 有了這個思路, 筆者就嘗試着找到TEXT()宏, 經過搜索引擎的幫助, 終於找到TEXT宏是定義在WINNT.H頭文件中, 找到了相關的定義如下:
#define __TEXT(quote) L##quote // r_winnt //與 #define TEXT(quote) __TEXT(quote) // r_winnt
當使用wchar_t類型的寬字符時, 使用"令牌粘貼", 這樣, 使用TEXT宏時就避免了在字符串前面加上一個大寫字母'L'了。
--------------------
Wid, 2012.10.07
