C++之編碼問題(Unicode,ASCII,本地默認)


本篇文章試圖回答的問題:

1、char* pStr="我aa";這句代碼執行后,pStr指向的內存區域中存儲的字節到底是根據什么碼表而來的呢?該字符串占幾個字節?

2、將一個VS2010的Windows程序設置了“使用Unicode字符集”到底意味着什么?

3、現在有一個文件,其存儲內容未知(可能是文本,可能是圖像,可能是視頻),要求是:在文件最前面插入一串Unicode文本,插入完成后以文本程序打開該文件,插入的文本不會顯示為亂碼(該文件本身的內容不考慮)。——如何做到?

本人能力、精力有限,所言所感都基於自身的實踐和有限的閱讀、查閱,如有錯誤,歡迎拍磚,敬請賜教——博客園:錢智慧。

一:

用VS2010新建一個win32控制台應用程序TestChar,代碼如下:

復制代碼
 1 #include <iostream>
 2 using namespace std;
 3 int main()
 4 {
 5     char* pStr="我aa";
 6     cout<<sizeof("我aa")<<endl;
 7     cout<<hex<<pStr[0]-0;
 8     cout<<pStr[1]-0<<endl;
 9     return 0;
10 }
復制代碼

打印結果如下:

分析:存儲介質(內存、外存等)上存儲的都是二進制數據,而對於字符信息的存儲,先查碼表進行解碼,再把碼以二進制信息存儲,即pStr指向的這段內存中存儲的都是字符們的編碼:一個中文字符,一個英文字母,一個全角字母。要得到”我“的編碼,默認會查找本地碼表,本人是中文Win7系統,查找的是GB2312碼表,而對照GB2312碼表可發現,"我"的編碼正是ced2,與打印一致(f是符號位,可無視)。另外GB2312是不會對英文字母進行編碼的,因為英文字母屬於半角字符,這類編碼由ASCII碼表負責,GB2312中的任何字符都占用兩個字節,空字符也由ASCII負責編碼,這就是為何上面的字符串占用的字節數目是6。可見,上面一句代碼,其實涉及到了兩張碼表:ASCII碼表和GB2312。

二:

新建一個MFC對話框程序,名為TestUnicodeChar。默認情況下,VS2010建的項目都是基於Unicode的,即打開項目的屬性,在”字符集“設置中都是”使用Unicode字符集“,這到底是什么意思呢?莫非在程序中用的中文都是以Unicode字節進行存儲的?由上面的TestChar程序可以看出並非如此,pStr中的"我”是以GB2312進行存儲的。又或者,源文件(h文件、cpp文件等)在磁盤上是以Unicode進行編碼存儲的?也不是,源文件存儲默認也是以本地GB2312進行存儲的。驗證方式:用UE編輯器隨便打開一個設置了“使用Unicode字符集”項目的某個源文件,比如打開本項目中的TestUnicodeCharDlg.cpp文件,在UE中以16進制的形式查看該文件內容,可以發現其前兩個字節並非FF FE(這是Unicode文件的標識),你隨便找一個中文,對照其16進制找到其編碼,然后跟GB2312碼表中的該中文的編碼對照即可驗證。這是一個GB2312碼表的網頁鏈接:

http://tool.xker.com/gb2312tbl.php 

我們知道,在C++中,有char和string,為了支持Unicode字符,還有wchar_t和wstring,我們可以認為string是基於char的,而wstring是基於wchar_t的。為什么要引入Unicode呢,只用char和string難道不能保存含有中文的字符(串)嗎?由TestChar程序,我們知道完全可以,並且采用的是GB2312編碼,問題是你無法確定一個類似pStr的混合串的字符數目,比如:string str="我a",你調用string的length方法不能准確得到str的長度,這給編程帶來了不便。所以你可以選用wstring和wchar:wstring wstr=L"我a",這時你再調用wstring的length方法就能准確得到了,其中L前綴可以使后面緊跟的字符串解釋成寬字符串(Unicode)。因為Unicode對任何字符都采用2個字節進行編碼,所以length的實現想必也很簡單:每兩個字節算一個字符,進而可以方便得到字符串的長度,這便是Unicode優於多字節編碼的地方:試想,如果采用多字節編碼,那么要實現基於這種編碼的字符串類的length方法會非常頭疼。Unicode浪費了存儲空間但帶來了編程上的簡便。(關於這方面的詳細內容可以參考《Windows程序設計 第5版》第1章)

在Windows中,有這樣一些宏:_T,TEXT,TCHAR,CString,它們根據不同的設定有不同的含義。先看一下它們的使用:

在TestUnicodeCharDlg.cpp的OnPaint方法中加上如下代碼:

1 TCHAR * pStr=TEXT("a我");//_T與TEXT的含義是一樣的
2 CString str1=TEXT("a我");
3 CString str2=L"a我";

如果程序定義了UNICODE宏,則_T和TEXT(二者含義和用法完全一樣)便會把括號內的字符串解釋為UNICODE字符串,TCHAR便會替換為wchar_t,CString便會替換為CStringW,否則(即沒有定義UNICODE宏),都會解釋為相應的char版本。而L前綴不是宏,類似強轉:不管有沒有定義UNICODE宏,都把后面的字符串解釋為UNICODE字符串。而設置“使用UNICODE字符集”就相當於定義UNICODE宏,即該設置僅僅是影響了一些宏的行為。若把該項目的“使用Unicode字符集”設置改為“未使用”,則編譯會出錯,因為此時str2就是一個CStringA實例,你不能把一個L前綴的字符串(Unicode字符串)賦值給它。

三:


第三個問題本質上就是往一個文件中寫Unicode字符串的問題。涉及到編碼問題的文件操作始終牢記一點:以什么編碼寫,就以編碼讀。在TestUnicodeChar程序的OnInitDialog函數中加入如下代碼:

復制代碼
 1 BOOL CTestUnicodeCharDlg::OnInitDialog()
 2 {
 3     CDialogEx::OnInitDialog();
 4 
 5     // 將“關於...”菜單項添加到系統菜單中。
 6 
 7     // IDM_ABOUTBOX 必須在系統命令范圍內。
 8     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 9     ASSERT(IDM_ABOUTBOX < 0xF000);
10 
11     CMenu* pSysMenu = GetSystemMenu(FALSE);
12     if (pSysMenu != NULL)
13     {
14         BOOL bNameValid;
15         CString strAboutMenu;
16         bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
17         ASSERT(bNameValid);
18         if (!strAboutMenu.IsEmpty())
19         {
20             pSysMenu->AppendMenu(MF_SEPARATOR);
21             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
22         }
23     }
24 
25     // 設置此對話框的圖標。當應用程序主窗口不是對話框時,框架將自動
26     //  執行此操作
27     SetIcon(m_hIcon, TRUE);            // 設置大圖標
28     SetIcon(m_hIcon, FALSE);        // 設置小圖標
29 
30     // TODO: 在此添加額外的初始化代碼
31     CFile myFile;
32 
33 
34     if ( myFile.Open( _T("c:\\myfile.txt"), CFile::modeCreate |   
35         CFile::modeReadWrite ) )
36     {
37 
38         CString str=TEXT("a我");
39         //myFile.Write("\xff\xfe",2);
40         myFile.Write( str, str.GetLength()*sizeof(TCHAR) ); 
41         myFile.Flush();
42     }
43     
44 
45     return TRUE;  // 除非將焦點設置到控件,否則返回 TRUE
46 }
復制代碼

運行程序,然后用寫字板、記事本、NotePad++、UE(UEdit)分別打開mfile.txt發現,有的能正常顯示,有的則亂碼。要知道,你往myfile.txt寫進去的是 兩個字符的Unicode編碼,用某文本程序去打開myfile.txt,倘若該程序默認情況下讀取文本時是按Unicode來解析的,則不會亂碼,否則就亂碼。我們把注釋的那行代碼的注釋拿掉,用任何支持Unicode的文本程序去打開myfile.txt就不會出錯了,因為一個文本中的前兩個字節FF FE便向試圖打開該文本的程序表明該文本應該用Unicode進行解析(你可以用NotePad++新建幾個Unicode格式的文本,隨便保存幾個字符,然后用UE以16進制格式查看便可知Unicode文本的前兩個字節都是FF FE)。

 

另外,經常遇到有人問這樣的問題:CString如何轉換為char*?問這個問題之前,最好問下自己:我的目的是什么,為何要進行這樣的轉換,當前項目有沒有設置Unicode。要知道,如果設置了Unicode,則CString存儲的是Unicode字符串,轉換為char*后,你如果直接顯示這個char*或者寫到文件中(沒有把FF FE寫到文件開始處)然后打開,則會(假如打開文件的程序默認不以Unicode進行解析)出現亂碼,所以,這種情況下,轉換為char*的意義不大——這不是說不能把Unicode串轉為char*,這完全是可行的,本質上這只是在把一個Unicode字符串的內存內容"活生生”取出來而已。不管怎樣,下面的代碼重新修改了OnInitDialog函數,演示了幾種情況:

復制代碼
 1 BOOL CTestUnicodeCharMFCDlg::OnInitDialog()
 2 {
 3     CDialogEx::OnInitDialog();
 4 
 5     // 將“關於...”菜單項添加到系統菜單中。
 6 
 7     // IDM_ABOUTBOX 必須在系統命令范圍內。
 8     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 9     ASSERT(IDM_ABOUTBOX < 0xF000);
10 
11     CMenu* pSysMenu = GetSystemMenu(FALSE);
12     if (pSysMenu != NULL)
13     {
14         BOOL bNameValid;
15         CString strAboutMenu;
16         bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
17         ASSERT(bNameValid);
18         if (!strAboutMenu.IsEmpty())
19         {
20             pSysMenu->AppendMenu(MF_SEPARATOR);
21             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
22         }
23     }
24 
25     // 設置此對話框的圖標。當應用程序主窗口不是對話框時,框架將自動
26     //  執行此操作
27     SetIcon(m_hIcon, TRUE);            // 設置大圖標
28     SetIcon(m_hIcon, FALSE);        // 設置小圖標
29 
30     // TODO: 在此添加額外的初始化代碼
31     CFile myFileW,myFileA,myFileCharArrow,myFileWOrA;
32 
33 
34     if ( myFileW.Open( _T("c:\\myfileW.txt"), CFile::modeCreate |   
35         CFile::modeReadWrite )  && 
36         myFileA.Open( _T("c:\\myfileA.txt"), CFile::modeCreate |   
37         CFile::modeReadWrite ) &&
38         myFileCharArrow.Open( _T("c:\\myfileCharArrow.txt"), CFile::modeCreate |   
39         CFile::modeReadWrite ) &&
40         myFileWOrA.Open( _T("c:\\myFileWOrA.txt"), CFile::modeCreate |   
41         CFile::modeReadWrite ))
42     {
43 
44         CString strW=TEXT("a我");//因為本項目設置了Unicode字符集,所以我們知道CString會被替換為CStringW
45         
46         CStringA strA(strW);
47         
48 
49         myFileW.Write( strW, strW.GetLength()*2); 
50         myFileW.Flush();
51 
52         myFileA.Write(strA,strA.GetLength());
53         myFileA.Flush();
54         //CString的GetString返回的const類型指針,要么在右邊強轉,要么左邊用const類型的char*去接
55         //注意指針命名:p是pointer,c是const,如果有t則是TEXT,w是wide,l是long
56         char* pstr=(char*)strA.GetString();
57         /*下面這行代碼若不注掉,會報錯,因為本程序是Unicode程序,
58         strW會是一個CStringW類型的字符串,它的GetString返回的是LPCWSTR類型指針
59         當然不能賦值給LPCSTR類型指針了,一個是const wchar_t*,另一個是const char*
60         */
61         //const char* pcstr1=strW.GetString();
62         
63         myFileCharArrow.Write(pstr,strlen(pstr));
64         myFileCharArrow.Flush();
65 
66         //假設我們在編程中,不知道有沒有使用Unicode設置,為了通用,我們可以盡量使用宏及通用版本的相關函數(如_tcslen)
67         CString strWOrA=TEXT("a我");
68         //注意這里的TCHAR不一定就是wchar_t,這取決於程序是否設置了Unicode
69         const TCHAR* pctstr=strWOrA.GetString();//CString的GetString返回的是const指針
70         myFileWOrA.Write(ptstr,_tcslen(pctstr)*sizeof(TCHAR));
71         myFileWOrA.Flush();
72 
73     }
74     
75 
76     return TRUE;  // 除非將焦點設置到控件,否則返回 TRUE
77 }
復制代碼

 

用UE察看幾個文件的內容,如圖:

其中,CED2是”我“的GB2312編碼,6211(注意字節高低次序)是”我“的Unicode編碼,我們可知,CStringA strA(strW)這行代碼,一定進行了碼表間的轉換。結合代碼,文件內容應該不難理解。

 

還有些讓人容易頭暈的字符串指針宏,下面列舉出來:

關於char*的:

LPCSTR: long pointer const string,可看成const char*,與PCSTR相似

LPSTR:可看成char*,與PSTR相似

關於wchar_t*的:

LPCWSTR,PCWSTR,LPWSTR,PWSTR

通用版本(根據是否配置了Unicode有不同的宏替換):

TCHAR*

LPTSTR,LPCTSTR (T有點類似TEXT宏的意思)

分類: C/C++
1
0
« 上一篇: C++之內部類(嵌套類)與外部類及友元
» 下一篇: 文件操作總結:關於文本和二進制流(typeText&typeBinary)
	</div>
	<div class="postDesc">posted @ <span id="post-date">2013-07-31 22:03</span> <a href="http://www.cnblogs.com/qzhforthelife/">錢智慧</a> 閱讀(<span id="post_view_count">16254</span>) 評論(<span id="post_comment_count">1</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=3228933" rel="nofollow">編輯</a> <a onclick="AddToWz(3228933);return false;" href="#">收藏</a></div>
</div>

posted on 2018-05-02 16:59  wynnfay  閱讀(9517)  評論(0編輯  收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM