最近寫程式的時候遇到了使用ifstream開啟含中文路徑檔案時失敗的問題,在網上翻了一下,發現這是一個普遍遇到的問題,在很多人的博文中也都給出了一些解決技巧,但大多是轉載的東西,很少對這個問題引發的原因有一個清晰、全面的解釋。因此,我覺得有必要對該問題引發的原因作一個詳細的剖析,希望對遇到同樣問題的朋友們能有所協助。
首先,用一個簡單的例子來重現一下我所遇到的問題:
(1)在VS2008的“Property Pages”屬性頁面中,選擇“Configuration Properties”-->“General”,可以看到當前使用的字元集是“Multi-Byte Character Set”,也就是說程式中使用的是多位元組字元集。
(2)接下來看看ifstream開啟txt檔案的簡單代碼:
[cpp]
view plaincopy
- #include "stdafx.h"
- #include <fstream>
- #include <iostream>
- using namespace std;
- int _tmain(int argc, _TCHAR* argv[])
- {
- ifstream infile("d://測試.txt");
- if(infile.is_open())
- {
- cout<<"Open Success!";
- }
- else
- {
- cout<<"Open Fail!";
- }
- return 0;
- }
(3)運行結果:輸出“Open Fail” (開啟檔案失敗!)
從設定選項中可以看到,工程中使用的字元集可設定為“Multi-Byte Character Set”或“Unicode Character Set”,其中“Multi-Byte Character Set”表示使用ANSI編碼方式,“Unicode Character Set”表示使用UNICODE編碼方式。
那麼這兩種編碼方式有什麼樣的區別呢?
(1)傳統的電腦使用ANSI編碼,在ANSI編碼模式下,英文字元都用1個位元組表示,而某些其它國家的文字(如漢字、日文),無法用單個位元組來表示,ANSI便採用多個位元組來表示這些字元(漢字是2個位元組)。
(2)UNICODE包含UTF-8、UTF-16、UTF-32等多種編碼方案(目前windows一般使用UTF-16)。拿UTF-16來說,規定所有字元都使用2個位元組表示(不論英文字母還是漢字),對於超出2個位元組範圍的字元採用代理(採用4個位元組表示)。
UNICODE相比ANSI有很多方面的優勢(優勢體現在哪?),微軟非常提倡使用UNICODE編碼方式,在MS較新版本的系統中都是採用UNICODE編碼的。因此,即便我們在自己寫的程式中使用了ANSI編碼,系統會將其轉換為UNICODE再對其進行處理。
接下來我們說一下ifstream。在調用ifstream的open方法時,系統內部調用mbstowcs_s進行檔案名稱轉換(mbstowcs_s函數的作用是把多位元組字元轉化為寬字元),需要注意的是,該函數的調用結果依賴於程式的本地化設定(什麼是本地化設定?)。而本地化設定可以通過setlocale函數來設定,譬如:setlocale(LC_ALL, "chinese")表示將程式本身的語言設定為中文,而程式啟動時預設設定為LC_ALL="C"。在使用mbstowcs_s進行字串轉換時,只有當LC_ALL="chinese"時,含中文的字串才能正確的轉換成其對應的寬位元組字元,否則(在LC_ALL="C"時),漢字會被看成2個單位元組的字元,然後再轉換成寬位元組的字元,這樣轉換的結果顯然是錯誤的!這就是ifstream開啟含中文路徑的檔案失敗的原因,因為"d://測試.txt"轉換後得到錯誤的路徑,因此檔案打不開!
解決方案如下:
1: /********************************************************************
2: created: 2008/05/10
3: created: 10:5:2008 23:56
4: filename: k:/sj/fstreamTest/fstreamTest/main.cpp
5: file path: k:/sj/fstreamTest/fstreamTest
6: file base: main
7: file ext: cpp
8: author: Gohan
9: *********************************************************************/
10: #include <tchar.h>
11: #include <fstream>
12: #include <iostream>
13: using namespace std;
14: int main()
15: {
16: /************************************************************************/
17: /* 方法1,使用_TEXT()宏定義將字串常量指定為TCHAR*類型 */
18: /* 如果是我,首選此類型 */
19: /************************************************************************/
20: fstream file;
21: file.open(_TEXT("c://測試//測試文本.txt"));
22: cout<<file.rdbuf();
23: file.close();
24:
25: /************************************************************************/
26: /* 方法2,使用STL中的locale類的靜態方法指定全域locale */
27: /* 使用該方法以後,cout可能不能正常輸出中文,十分蹊蹺 */
28: /* 我發現了勉強解決的方法:不要在還原地區設定前用cout或wcout 輸出中文 */
29: /* 否則後果就是還原地區設定後無法使用cout wcout輸出中文 */
30: /************************************************************************/
31: locale::global(locale(""));//將全域地區設為作業系統預設區域
32: file.open("c://測試//測試文本2.txt");//可以順利開啟檔案了
33: locale::global(locale("C"));//還原全域地區設定
34: cout<<file.rdbuf();
35: file.close();
36:
37: /************************************************************************/
38: /* 方法3,使用C函數setlocale,不能用cout輸出中文的問題解決方案同上 */
39: /************************************************************************/
40: setlocale(LC_ALL,"Chinese-simplified");//設定中文環境
41: file.open("c://測試//測試文本3.txt");//可以順利開啟檔案了
42: setlocale(LC_ALL,"C");//還原
43: cout<<file.rdbuf();
44: file.close();
45: }
參見部落格:http://www.cppblog.com/gohan/archive/2008/05/11/49488.html
由於windows提倡使用UNICODE編碼,因此,我們在使用VS編寫程式的時候,最好也都使用UNICODE字元集。這樣有利於避免字元集轉換帶來的問題,同時,也有利於提高效率(前面提到,windows內部會把ANSI編碼轉換為UNICODE再處理,這些轉換當然也帶來了額外的時間消耗)。
在樣本的程式中,可以將工程字元集設定為UNICODE,然後將字串前面加上_T(這樣,在字元集已經設定為UNICODE的情況下,該字串會自動採用寬字元表示),例如:ifstream infile(_T("d://測試.txt")),便不會有開啟檔案不成功的問題了。