C++ UNICODE 文件讀寫相關

熟悉一下字符類型,char, wchar_t, TCHAR,最熟悉的char是單字節字符,適用於ANSI編碼;wchar_t是雙字節的寬字符類型,適用於unicode編碼;TCHAR是一個宏,在ANSI壞境下定義爲char,unicode壞境下定義爲wchar_t。

怎麼來表示字符串?對,字符數組,要知道在C++語言裏面,其實沒有數組的數據結構,所謂數組,都是由指針+長度來表示。字符型指針const char *, const wchar_t *, const TCHAR *可以用來在不同的環境下表示字符串。再說相關的幾個宏,LPSTR: long point string, 相當於char *; LPCSTR: long point const string, 相當於 const char *; LPCWSTR: long point const wide string, 相當於 const wchar_t *; LPCTSTR: 類似的,相當於 const TCHAR *; 這些都不要死記硬背,記着大寫字母的意思即可猜出其含義。

一個字符串,比如說"北京2008",對應ANSI編碼表示爲 const char * cha = "北京2008"; unicode編碼表示爲 const wchar_t * wcha = L"北京2008"; 。在內存裏以二進制存儲,ANSI編碼對應爲 0x B1B1 BEA9 32 30 30 38,unicode編碼爲 0x 1753 AC4E 3200 3000 3000 3800。

回到上面,爲什麼字符型指針可以表示一個字符串?計算機找到這個指針,只能知道串首字符,這裏因爲字符串有個默認的結束符'\0'(ANSI或者ASCII表示爲0x00),從首字符開始,計算機開始向後查找直到0x00,認爲字符串結束,所以存儲字符串的時候,計算機是帶着一個特殊結束符的。可是要注意了,這個結束符0x00是ASCII碼定義的結束符啊,那麼在寬字符unicode環境下呢?結束符是什麼?是0x0000。

而對於非const字符串,怎麼表示?char * 方法怎麼動態定義長度?好辦,可以用new手動分配內存空間,除此之外,還有更好辦的方法,那就是字符串類型string, 怎麼可變長度,怎麼記錄長度,內存怎麼存儲,這些都不用管,都有C++標準庫自動管理。

不同類型的字符串間之間怎麼轉換?比如定義 char * cha; string str; str = cha; // 可以實現 char * 到 string 的轉換, cha = str.c_str(); 可以從 string 轉換到 char *;對於wchar_t wcha; wstring wstr; 呢?wstr = wcha; wcha = wstr.c_str(); // 這個是否可以呢?!

說過了字符串的表示和類型轉換,再來看字符流I/O,C++裏面的fstream, ifstream, ofstream, 文件流的I/O有好多種方式,默認爲字符流方式,明確的說是ANSI字符流,都是針對ANSI文本的,那麼unicode怎麼讀寫呢?

C++裏倒真有wfsteam流的,可惜用起來也很奇怪,用wifstream讀取unicode文本,結果竟然是讀取一個字節,加上一個0x00,在讀取下一個字節,如此!比如文本里保存的還是“北京2008”,剛纔說過unicode編碼爲 0x 1753 AC4E 3200 3000 3000 3800;用wifstream讀到內存的字符竟是 0x 1700 5300 AC00 4E00 ... 這叫什麼unicode?我不知道wfstream怎麼正確使用用,有知道的朋友還請不吝告知!

既然wftream不行,那麼怎麼讀取unicode呢,這裏可以借鑑一下二進制流的讀寫方式,二進制流在讀寫時必須明白存儲單位的數據結構,定義爲結構體,然後逐n字節(n爲結構長度)按二進制讀取;這個可以借鑑過來,不用定義結構了,直接用wchar_t,代碼如下:
ifstream fin;
fin.open(filename, ios::binary);
// 跳過unicode文本開頭有兩個字節0xFFFE(稱作BOM,用於標識unicode編碼)
fin.seek(2, ios::beg);
while (!fin.eof())
{
wchar_t wch;
fin.read((char *)(&wch), 2);
}

如果要按行讀取,怎麼辦?好了,有ifstream的成員函數getline(cha, size),還有string類成員函數getline(fin, str)。你試試能不能用在unicode下使用?答案是否定的!爲什麼?因爲getline函數默認在ANSI下使用,它對換行符的判斷是基於ASCII碼的換行(0x0D)和行開頭標記(0x0A),如果把它用在unicode編碼下,比如“不”字,unicode編碼爲0x0D4E。當getline函數執行到這,以爲換行了,所以說會失效!那麼unicode換行符以及行開頭符的二進制是什麼?雙字節了,是0x0D00和0x0A00,這時候getline函數就失效了,怎麼辦,手動判斷:
ifstream fin;
fin.open(filename, ios::binary);
size_t index = 2;
while (!fin.eof())
{
fin.seekg(index, ios::beg);
wchar_t wch;
fin.read((char *)(&wch), 2);
if (wch == 0x000D) // 判斷回車
{
strLineAnsi = ws2s(wstrLine);
wstrLine.erase(0, wstrLine.size() + 1);
iLine++;
index += 4; // 跳過回車符和行開頭符
}
else
{
wstrLine.append(1, wch);
index += 2;
}
}

上面的程序可以讀取unicode了,那麼讀了進來怎麼理解unicode呢,這就需要char * 和 wchar_t *間的轉換了,這個沒有簡便的方法,ANSI、UNICODE兩種編碼之間的轉換,只能靠查表實現,C++提供了兩個函數,wcstombs(_Dest, _Source, _Dsize) 從unicode編碼轉化爲ANSI編碼 ,mbstowcs(_Dest, _Source, _Dsize)反之,參數對應爲const char*, const wchar_t*以及長度。這裏在提供一個網上的函數,用於實現string和wstring的轉換:
std::string ws2s(const std::wstring& ws)
{
std::string curLocale = setlocale(LC_ALL, NULL); // curLocale = "C";
setlocale(LC_ALL, "chs");
const wchar_t* _Source = ws.c_str();
size_t _Dsize = 2 * ws.size() + 1;
char *_Dest = new char[_Dsize];
memset(_Dest,0,_Dsize);
wcstombs(_Dest,_Source,_Dsize);
std::string result = _Dest;
delete []_Dest;
setlocale(LC_ALL, curLocale.c_str());
return result;
}

std::wstring s2ws(const std::string& s)
{
setlocale(LC_ALL, "chs");
const char* _Source = s.c_str();
size_t _Dsize = s.size() + 1;
wchar_t *_Dest = new wchar_t[_Dsize];
wmemset(_Dest, 0, _Dsize);
mbstowcs(_Dest,_Source,_Dsize);
std::wstring result = _Dest;
delete []_Dest;
setlocale(LC_ALL, "C");
return result;
}

寫到這裏,就可以用C++讀取unicode文本了,寫的方法類似。

本文轉載自:https://blog.csdn.net/zhuxianjianqi/article/details/21888687

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章