1. 程式人生 > >C++ UNICODE 檔案讀寫相關

C++ UNICODE 檔案讀寫相關

<致敬原作者>http://librawill.blogspot.com/2008/08/cunicode_2881.html
熟悉一下字元型別,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文字了,寫的方法類似。