設置 | 登錄 | 註冊

目前共有4篇帖子。

【程序】給C++的cout和fstream添加Unicode支持,使其能向屏幕或文件輸入/輸出wchar_t字符串

1樓 巨大八爪鱼 2016-7-16 14:58
【程序】
#include <fstream>
#include <iostream>
#include <Windows.h>

#define RDBUF_LEN 200

using namespace std;

ostream &operator << (ostream &os, const wchar_t *wstr)
{
    if (os == cout)
        WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), wstr, wcslen(wstr), NULL, NULL); // 輸出到屏幕上
    else
    {
        // 此時wstr是UTF-16編碼
        // 將其轉換為UTF-8編碼後寫入文件
        int n = WideCharToMultiByte(CP_UTF8, NULL, wstr, -1, NULL, NULL, NULL, NULL);
        char *p = new char[n];
        WideCharToMultiByte(CP_UTF8, NULL, wstr, -1, p, n, NULL, NULL);
        os.write(p, n - 1);
        delete[] p;
    }
    return os;
}

ostream &operator << (ostream &os, wstring &ws)
{
    os << ws.c_str();
    return os;
}

istream &operator >> (istream &is, wstring &ws)
{
    int n = 0;
    wchar_t *wp;
    if (is == cin)
    {
        /* 從控制台中輸入一行字符串 */
        bool complete = false;
        DWORD dwRead; // 實際讀到的字符個數
        HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
        wp = NULL;
        do
        {
            /* 分次循環讀取, 直到遇到換行符 */
            wp = (wchar_t *)realloc(wp, (n + RDBUF_LEN + 2) * sizeof(wchar_t)); // 分配內存時始終為\r\n預留空間
            ReadConsoleW(hInput, wp + n, RDBUF_LEN + 2, &dwRead, NULL);
            if (wp[n + dwRead - 2] == '\r')
            {
                wp[n + dwRead - 2] = '\0'; // 把\r換成\0, 並結束讀取
                complete = true;

                // 注意: 當本次只讀到\n一個字符時, wp[n + dwRead - 2]指向上一次讀到的那一段的最後一個字符, 且一定是\r
                // 例如: [abc][de\r][\n  ], 此時RDBUF_LEN=1, 第二次讀的時候n=3, dwRead=3, wp[3+3-2]=wp[4]是e而不是\r, 所以繼續
                // 第三次讀的時候n=6, dwRead=1, wp[6+1-2]=wp[5]恰好是\r, 因此將\r替換成\0並退出循環
            }
            else
                n += dwRead;
        } while (!complete);
        ws = wp;
        free(wp);
    }
    else
    {
        /* 從文件中讀取一行字符串 */
        streampos start_pos = is.tellg(); // 記錄開始讀取的位置
        char ch;
        while (ch = is.get(), !is.eof() && ch != '\r' && ch != '\n')
            n++; // 尋找換行符或文件結束符出現的位置
        is.seekg(start_pos); // 回到開始位置

        // 讀取這一部分內容
        char *p = new char[n + 1];
        is.read(p, n);
        p[n] = '\0';

        // 使文件位置指針跳過換行符
        while (ch = is.get(), !is.eof() && (ch == '\r' || ch == '\n'));
        if (!is.eof())
            is.seekg(-1, ios::cur);

        // 轉換成UTF-16編碼
        n = MultiByteToWideChar(CP_UTF8, NULL, p, -1, NULL, NULL);
        wp = new wchar_t[n];
        MultiByteToWideChar(CP_UTF8, NULL, p, -1, wp, n);
        delete[] p;
        ws = wp;
        delete[] wp;
    }
    return is;
}

int main(void)
{
    // 從控制台中輸入Unicode字符串
    wstring wstr;
    cout << L"請輸入一段文字: ";
    cin >> wstr;

    // 打開文件並寫入Unicode字符串
    ofstream file("file.txt");
    if (!file.is_open())
    {
        cout << L"文件打開失敗" << endl;
        return 0;
    }
    file << L"1234" << endl;
    file << L"簡體中文abc" << endl;
    file << L"¿Cómo estás?" << endl;
    file << L"用戶輸入: " << wstr << endl;
    cout << L"已成功寫入" << file.tellp() << L"字節" << endl;
    file.close();

    // 打開文件並從文件中讀取Unicode字符串
    ifstream ifile("file.txt");
    cout << endl << endl << endl << L"**********************文件中的內容*********************" << endl;
    while (!ifile.eof())
    {
        ifile >> wstr;
        cout << wstr << endl; // 顯示
    }
    ifile.close();
    return 0;
}
【運行結果】

2樓 巨大八爪鱼 2016-7-16 14:58
【輸出的文件】

3樓 巨大八爪鱼 2016-7-16 15:14
【背景知識】
char字符數組能存放任意編碼的字符串
wchar_t在Windows系統下一般用來存放UTF-16編碼的字符串,Linux下一般是UTF-32字符串
在C/C++中,"xxx"型的字符串的編碼與源文件的編碼格式一致,而L"xxx"則統一為UTF-16(Windows) / UTF-32(Linux)編碼。
在Visual Studio中默認創建的源文件的編碼格式是ANSI編碼,所以char xxx[] = "xxx"的編碼一般都是ANSI。
ANSI的編碼格式並不固定,它只是一個編碼映射。在簡體中文Windows作業系統下,ANSI指向的是GB2312編碼。由於英文版系統下ANSI並不指向GB2312編碼,所以打開相關的文本文件就會顯示為亂碼,而打開UTF-8編碼的文本文件就不會亂碼。
Windows API函數都分為ANSI和Unicode版本。例如SetWindowTextA接受的是char字符數組,因為char字符數組一般都是ANSI編碼,而SetWindowTextW接受的是wchar_t字符數組(默認UTF-16編碼)
UTF-8、UTF-16和UTF-32是Unicode的三種實現方式。Unicode負責給每個字符編號,而這三種實現方式則按不同的方式存儲其編號。

C/C++中自帶的printf, cout等庫函數都不能正確輸出Unicode的字符串,因為它們在輸出前都會將Unicode轉換成ANSI,導致字符信息丟失,並且這個過程無法阻止。要想輸出Unicode字符串只有調用Windows的API函數:WriteConsoleW。對於Unicode字符串的輸入也有同樣的問題。
本文就是把這個功能封裝到了C++的cout裡面。
4樓 巨大八爪鱼 2016-7-16 23:13
【補充內容】
給C語言的printf添加Unicode支持的方法請參考:
https://zh.arslanbar.net/post.php?t=24206&p=1

內容轉換:

回覆帖子
內容:
用戶名: 您目前是匿名發表。
驗證碼:
看不清?換一張
©2010-2025 Purasbar Ver3.0 [手機版] [桌面版]
除非另有聲明,本站採用知識共享署名-相同方式共享 3.0 Unported許可協議進行許可。