1. 程式人生 > 實用技巧 >【超詳細】MakeDown(Typora)+PicGo+Gitee實現圖床

【超詳細】MakeDown(Typora)+PicGo+Gitee實現圖床

ASCII及其擴充套件(字符集)

ASCII

ASCII使用 7位二進位制位 來表示一個字元,總共可以表示128個字元(即2^7,二進位制000 0000 ~ 111 1111 十進位制0~127)。

ASCII 擴充套件一

需求 :ASCII字符集是美國人發明的,這些字元完全是為其量身定製的。但隨著計算機技術的發展和普及,傳到了歐洲(如法國、德國)各國。由於歐洲很多國家中使用的字元除了ASCII表中的128個字元之外,還有一些各國特有的字元,於是歐洲人民發現ASCII字符集表達不了他們所要表達的東西呀。怎麼辦?

解決 :ASCII只使用了一個位元組(8位)之中的低7位,於是歐洲各國開始各顯神通,打起了那1個最高位(第0位)的主意,將 最高位

利用了起來,這樣又多了128個字元,從而滿足了歐洲人民的需要。

ASCII 擴充套件二(多位元組擴充套件)

編碼名稱 釋出時間 位元組數 漢字範圍
GB2312 1980 變位元組(ASCII 1位元組,漢字2位元組 6763
GB13000 1993 變位元組(ASCII 1位元組,漢字2位元組 20902
GBK(CP936) win95 2個位元組 21886
GB18030 2000 變位元組(ASCII 1位元組,漢字2位元組或4位元組 27484

ASCII 擴充套件三 (ANSI)

世界各國針對ASCII的擴充套件方案(如歐洲的ISO/IEC 8859,中國的GB系列等),這些 ASCII擴充套件編碼方案

的特點是:他們都相容ASCII編碼,但他們彼此之間是不相容的。微軟將這些編碼方案統稱為ANSI編碼。

在windows作業系統上,預設使用ANSI來儲存檔案。那麼作業系統是如何知道ANSI到底應該表示哪種編碼了,是GBK,還是ASCII,或者還是EUC-KR了? windows通過一個叫 "Code Page" (翻譯為中文就叫內碼表)的東西來判斷系統的預設編碼。簡體中文作業系統預設的內碼表是936,它表示ANSI使用的是GBK編碼。GB18030編碼對應的windows內碼表為CP54936。

可以使用 命令chcp 來檢視系統預設的內碼表。

Unicode統一碼標準(字元編碼)

需求 :各個國家使用不同的編碼規則,雖然他們都是相容ASCII的,但它們相互卻是不相容的。
Unicode字符集和ASCII字符集一樣,也只是一個字元集合,標記著字元和數字之間的對映關係,它不包含任何編碼規則和方案。和ASCII不一樣的是,Unicode字符集支援的字元數量是沒有限制的。

那麼Unicode字元是怎樣被編碼成記憶體中的位元組的了?它是通過UTF(Unicode Transformation Formats)實現的,比較常見得有UTF-8,UTF-16。

UTF-8

UTF-8編碼規則

  1. 對於ASCII(單位元組字元)字元,採用和ASCII相同的編碼方式,即只使用一個位元組表示,且該位元組第一位為0.
  2. 對於多位元組(2~4位元組)字元,假設位元組數為n(1 < n <= 4),第一個位元組:前n位都設為1,第n+1位設為0;後面的n-1個位元組的前兩位一律設為10。所有位元組中的沒有提及的其他二進位制位,全部為這個符號的unicode碼。

UTF-8帶BOM(Byte Order Mark)

“微軟在自己的UTF-8格式的文字檔案之前加上了EF BB BF三個位元組, windows上面的notepad等程式就是根據這三個位元組來確定一個文字檔案是ASCII的還是UTF-8的, 然而這個只是 微軟暗自作的標記 , 其它平臺上並沒有對UTF-8文字檔案做個這樣的標記。”

編碼 ‘ABC’
UTF-16BE(Without BOM) 00 41 00 42 00 43
UTF-16LE(Without BOM) 41 00 42 00 43 00
UTF-16BE(Without BOM) FE FF 00 41 00 42 00 43
UTF-16LE(Without BOM) FF FE 41 00 42 00 43 00

在windows系統上漢字預設使用CP936(即GBK編碼),佔2個位元組。而大多數Unicode字元的Unicode碼值也佔2個位元組,所以大多數人誤以為漢字字串在記憶體中的值就是Unicode值,這是錯誤的。
可以從 站長工具-Unicode 查詢漢字的Unicode碼值。

其它

全形與半形

因為漢字在顯示器上的顯示寬度要比英文字元的寬度要寬一倍,在一起排版顯示時不太美觀。所以GB編碼不僅僅加入了漢字字元,而且包括了ASCII字符集中本來就有的數字、標點符號、字母等字元。這些被編入GB編碼的數字、標點、字母在顯示器上的顯示寬度比ASCII字符集中的寬度寬一倍,所以前者稱為全形字元,後者稱為半形字元。

Windows 轉換介面

Windows API

//寬位元組(Windows內UTF-16) -> 多位元組編碼(ASCII/UTF-8等)
int WideCharToMultiByte(
  UINT CodePage, 
  DWORD dwFlags, 
  LPCWSTR lpWideCharStr, 
  int cchWideChar, 
  LPSTR lpMultiByteStr, 
  int cbMultiByte, 
  LPCSTR lpDefaultChar, 
  LPBOOL lpUsedDefaultChar 
);
// 多位元組編碼(ASCII/UTF-8等) -> 寬位元組(Windows內UTF-16)
int MultiByteToWideChar(
  UINT CodePage, 
  DWORD dwFlags, 
  LPCSTR lpMultiByteStr, 
  int cbMultiByte, 
  LPWSTR lpWideCharStr, 
  int cchWideChar 
);

介面封裝

std::string UnicodeToANSI(const std::wstring &str, UINT iCodePage = CP_ACP) {
    std::string strRes;
    int iSize = ::WideCharToMultiByte(iCodePage, 0, str.c_str(), -1, NULL, 0, NULL, NULL);

    if (iSize == 0)
        return strRes;

    char *szBuf = new (std::nothrow) char[iSize];
    if (!szBuf)
        return strRes;
    memset(szBuf, 0, iSize);

    ::WideCharToMultiByte(iCodePage, 0, str.c_str(), -1, szBuf, iSize, NULL, NULL);

    strRes = szBuf;
    delete[] szBuf;

    return strRes;
}

std::wstring ANSIToUnicode(const std::string &str, UINT iCodePage = CP_ACP) {
    std::wstring strRes;

    int iSize = ::MultiByteToWideChar(iCodePage, 0, str.c_str(), -1, NULL, 0);

    if (iSize == 0)
        return strRes;

    wchar_t *szBuf = new (std::nothrow) wchar_t[iSize];
    if (!szBuf)
        return strRes;
    memset(szBuf, 0, iSize * sizeof(wchar_t));

    ::MultiByteToWideChar(iCodePage, 0, str.c_str(), -1, szBuf, iSize);

    strRes = szBuf;
    delete[] szBuf;

    return strRes;
}

std::string UnicodeToUTF8(const std::wstring &str) {
    std::string strRes;

    int iSize = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, NULL, 0, NULL, NULL);

    if (iSize == 0)
        return strRes;

    char *szBuf = new (std::nothrow) char[iSize];
    if (!szBuf)
        return strRes;
    memset(szBuf, 0, iSize);

    ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, szBuf, iSize, NULL, NULL);

    strRes = szBuf;
    delete[] szBuf;

    return strRes;
}

std::string UnicodeToUTF8BOM(const std::wstring &str) {
    std::string strRes;

    int iSize = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, NULL, 0, NULL, NULL);

    if (iSize == 0)
        return strRes;

    unsigned char *szBuf = new (std::nothrow) unsigned char[iSize + 3];
    if (!szBuf)
        return strRes;
    memset(szBuf, 0, iSize + 3);

    if (::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, (LPSTR)(szBuf + 3), iSize, NULL, NULL) > 0) {
        szBuf[0] = 0xEF;
        szBuf[1] = 0xBB;
        szBuf[2] = 0xBF;
    }

    strRes = (char*)szBuf;
    delete[] szBuf;

    return strRes;
}

std::wstring UTF8ToUnicode(const std::string &str) {
    std::wstring strRes;
    int iSize = ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);

    if (iSize == 0)
        return strRes;

    wchar_t *szBuf = new (std::nothrow) wchar_t[iSize];
    if (!szBuf)
        return strRes;
    memset(szBuf, 0, iSize * sizeof(wchar_t));
    ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, szBuf, iSize);

    strRes = szBuf;
    delete[] szBuf;

    return strRes;
}

std::string ANSIToUTF8(const std::string &str, UINT iCodePage = CP_ACP) {
    return UnicodeToUTF8(ANSIToUnicode(str, iCodePage));
}

std::string ANSIToUTF8BOM(const std::string &str, UINT iCodePage = CP_ACP) {
    return UnicodeToUTF8BOM(ANSIToUnicode(str, iCodePage));
}

std::string UTF8ToANSI(const std::string &str, UINT iCodePage = CP_ACP) {
    return UnicodeToANSI(UTF8ToUnicode(str), iCodePage);
}

在windows下避免亂碼

通過第3節的說明,很容易知道,要開發支援多語言,在任意語言(系統內碼表)的windows環境下都正常編譯,且執行起來沒有亂碼的程式,需要遵循如下原則:

  1. 程式碼檔案採用UTF-8 with BOM編碼。
  2. Visual Studio字符集設定為Unicode字符集。
  3. 使用wchar_t。

參考網站

撥開字元編碼的迷霧--字元編碼概述
撥開字元編碼的迷霧--MySQL資料庫字元編碼