欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++代碼改造為UTF-8編碼問題的總結(jié)(最新推薦)

 更新時(shí)間:2025年02月13日 08:47:00   作者:charlee44?博客園VIP會(huì)員  
本文總結(jié)了如何將C++程序代碼改造為UTF-8編碼,包括操作系統(tǒng)、編譯器和終端等各方面的設(shè)置,在實(shí)際操作中,可以通過漸進(jìn)式更新的方式,只在新的代碼項(xiàng)目中使用UTF-8編碼,避免大規(guī)模修改舊代碼,感興趣的朋友一起看看吧

1. 引言

無論是哪個(gè)平臺(tái)哪種編程語言,字符串亂碼真是一個(gè)讓人無語的問題:你說這個(gè)問題比較小吧,但是關(guān)鍵時(shí)刻來一下真是受不了。解決方式也有很多種,但是與其將編碼轉(zhuǎn)換來轉(zhuǎn)換去,不如統(tǒng)一使用同一種編碼方式,比如國際通用的UTF-8編碼。因此,新的程序代碼最好都統(tǒng)一使用UTF-8編碼的方式。但是C++作為一種歷史悠久的編程語言,肯定存在很多存量代碼,如何將其改造成UTF-8編碼也是一個(gè)問題,筆者在這里總結(jié)一二,可能不是很全,如果有遺漏就再開一篇補(bǔ)充。

2. 詳述

2.1 操作系統(tǒng)

統(tǒng)一使用統(tǒng)一使用UTF-8編碼還有個(gè)好處是跨平臺(tái)。但是操作系統(tǒng)本身也是有字符編碼的,這會(huì)影響到與操作系統(tǒng)相關(guān)的應(yīng)用,比如說終端。Linux系統(tǒng)一般不用擔(dān)心,目前一般都默認(rèn)使用UTF-8編碼。Windows系統(tǒng)則有點(diǎn)麻煩,一般使用ANSI碼(本地碼)。本地碼的意思就是基于當(dāng)前系統(tǒng)區(qū)域設(shè)置的字符編碼,以國內(nèi)大陸的來說就是國標(biāo)碼:GB2312/GBK/GB18030。這就是為什么Windows的終端總是出現(xiàn)亂碼的原因,因?yàn)榫幋a不一致:GBK編碼的終端遇到UTF-8編碼字符串當(dāng)然不會(huì)正確展示了。

當(dāng)然現(xiàn)在Windows系統(tǒng)也能設(shè)置成UTF-8編碼了,如下圖1所示。但是還是建議不要輕易這么設(shè)置,Windows系統(tǒng)沒有將UTF-8編碼設(shè)置系統(tǒng)的默認(rèn)編碼主要也是為了保證兼容性,在Unicode編碼大規(guī)模使用之前本地碼還是使用了相當(dāng)長的時(shí)間的,有相當(dāng)數(shù)據(jù)量的遺留程序都是使用的本地碼。為了避免大規(guī)模應(yīng)用程序亂碼問題的出現(xiàn),不能要求每個(gè)用戶都這么設(shè)置。

2.2 編譯器

雖然最好不要在操作系統(tǒng)層面設(shè)置成UTF-8編碼,但是還是可以編寫基于UTF-8編碼的程序的。將代碼文件修改成UTF-8編碼是一方面,另外一方面是編譯器要將代碼文件按照UTF-8編碼進(jìn)行編譯。因?yàn)闊o論是ASCII、GB18030還是UTF-8編碼的文本文件,其實(shí)都是沒有具體的標(biāo)識(shí)符的,編譯器需要知道以哪種字符編碼來編譯代碼文件中的字符。

Linux系統(tǒng)還是不用擔(dān)心,默認(rèn)情況下文本文件通常使用UTF-8編碼,GCC編譯器也會(huì)默認(rèn)使用系統(tǒng)的默認(rèn)字符編碼也就是UTF-8編碼來進(jìn)行編譯。麻煩的還是Windows系統(tǒng),暫時(shí)不討論各種復(fù)雜的情況,筆者以Visual Studio的MSVC編譯器為例,介紹一下自己的做法。

首先還是要將代碼文件修改成UTF-8編碼,這里推薦使用Visual Studio的一個(gè)擴(kuò)展:FileEncoding,它可以很方便的在代碼頁面的右下角修改代碼文件編碼,如下圖2所示。不過有一點(diǎn)要注意,選擇使用UTF-8編碼而不是UTF-8(BOM)編碼。

然后是給MSVC編譯器增加一個(gè)編譯選項(xiàng):/utf-8,這個(gè)編譯選項(xiàng)會(huì)將源代碼字符集和執(zhí)行字符集指定為使用UTF-8編碼字符集。具體來說,如果你是原生的MSVC的項(xiàng)目,應(yīng)該執(zhí)行的操作是:

  • 打開項(xiàng)目“屬性頁” 對(duì)話框。
  • 依次選擇“配置屬性”->“C/C++”->“命令行”屬性頁。
  • 在“附加選項(xiàng)”中,添加/utf-8選項(xiàng)以指定首選編碼。
  • 選擇“確定”以保存更改。

如果是CMake項(xiàng)目,那么在CMakeLists.txt中增加如下配置,意思是如果是MSVC編譯器,就增加/utf-8選項(xiàng):

# 判斷編譯器類型
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
    message(">> using Clang")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    message(">> using GCC")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")
    message(">> using Intel C++")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
    message(">> using Visual Studio C++")
    add_compile_options("/utf-8")
else()
    message(">> unknow compiler.")
endif()

最后,還需要考慮一點(diǎn),字符最終需要顯示到終端的,無論是GUI終端還是命令行終端,你必須確保終端的字符編碼也是UTF-8編碼才行。例如打印字符串到命令行終端,可使用如下示例代碼(C++17環(huán)境下):

#include <iostream>
#ifdef _WIN32
#include <Windows.h>
#endif
using namespace std;
int main() {
#ifdef _WIN32
  SetConsoleOutputCP(65001);
#endif
  string str = "這是中文字符串,測(cè)試能否正確顯示!";
  std::cout << str << endl;
  return 0;
}

這段代碼的意思是在Windows環(huán)境下,設(shè)置控制臺(tái)輸出窗口的代碼頁是65001,也就是UTF-8編碼。同時(shí)由于代碼文件是UTF-8編碼,字符串常量"這是中文字符串,測(cè)試能否正確顯示!"也是UTF-8編碼。std::string與具體的字符編碼無關(guān),它只是個(gè)8位字符數(shù)組,因此可以接受UTF-8編碼的字符串并被打印輸出。

2.3 漸進(jìn)升級(jí)

按照以上步驟編寫新的基于UTF-8編碼的程序是沒有問題的,但是實(shí)際操作大概率不行。因?yàn)镃++程序往往有足夠多的存量代碼,我們往往需要以庫的形式或者組件的形式來調(diào)用它們。問題是C++程序調(diào)用庫是需要include頭文件的,一旦設(shè)置了/utf-8編譯選項(xiàng),MSVC就會(huì)強(qiáng)制將這些舊代碼按照UTF-8編碼進(jìn)行編譯。在這種情況下,有很大的概率會(huì)出現(xiàn)亂碼問題,或者出現(xiàn)如下編譯錯(cuò)誤:

warning C4828: 文件包含在偏移 0x66f 處開始的字符,該字符在當(dāng)前源字符集中無效(代碼頁 65001)。

一般而言,MSVC項(xiàng)目的存量代碼一般為本地編碼(GBK編碼),最直接的解決方案是一個(gè)一個(gè)地按照上述方式去升級(jí)這些代碼,但是這樣做就要看存量代碼有多少、是否有權(quán)限這么做了,如果工作量太大還是不建議這么做。比較合理的辦法還是漸進(jìn)式更新:

  • 只在新的代碼項(xiàng)目中使用UTF-8編碼的方式。
  • 舊的代碼項(xiàng)目還是使用GBK編碼。
  • 修改調(diào)用的舊代碼庫的頭文件,保證沒有非ASCII字符(中文字符)。

由于UTF-8編碼是兼容ASCII字符的,因此即使強(qiáng)制要求MSVC按照UTF-8編碼編譯這個(gè)文件,也是不會(huì)出現(xiàn)亂碼或者編譯不通過的問題的。并且這樣也是有可行性的,一般頭文件的代碼內(nèi)容很少,修改起來也不容易出錯(cuò)。其實(shí)在大部分情況下也確實(shí)不需要修改什么,大多數(shù)常用庫為了方便國際通用,頭文件很少出現(xiàn)非ASCII字符。

當(dāng)然這樣做也存在一個(gè)問題:舊的代碼接口是本地編碼,新的代碼卻是UTF-8編碼,調(diào)用的時(shí)候字符串傳參需要將UTF-8編碼轉(zhuǎn)換成GBK編碼字符串。但是這也是沒有辦法的辦法,只修改接口部分的代碼總比大規(guī)模修改程序好。想要完全避免字符編碼的問題就要統(tǒng)一使用UTF-8,最好按照這個(gè)原則,從調(diào)用端到底層框架逐漸將代碼都升級(jí)成UTF-8編碼。

3. 案例

所有接口統(tǒng)一使用UTF-8編碼真的是任何程序開發(fā)的金玉良言,否則就總是會(huì)遇到字符編碼轉(zhuǎn)換的問題,非常影響工作效率。不過可能因?yàn)榧嫒菪曰蛘咂渌颍壳斑€做不到將所有的接口統(tǒng)一編碼。筆者這里就列舉一些常用的組件和庫的接口的字符串編碼案例。

3.1 std::filesystem::path

個(gè)人認(rèn)為C++17的std::filesystem使用起來還是很方便的,但是std::filesystem::path的初始化并沒有如我所想統(tǒng)一使用UTF-8編碼。在Linux環(huán)境下初始化std::filesystem::path使用的確實(shí)是UTF-8編碼字符串,但是在Windows環(huán)境下,初始化需要使用UTF-16編碼字符串。例如一個(gè)初始化路徑的跨平臺(tái)代碼:

#ifdef _WIN32
std::filesystem::path launchConfigPath =
   L"C:/Github/中文路徑/launch-config.json";
#else
std::filesystem::path launchConfigPath =
   "/home/Github/中文路徑/launch-config.json";
#endif

在MSVC編譯器中,以L開頭的字符串是一個(gè)寬字符字符串,對(duì)應(yīng)于UTF-16編碼。而如果本身是一個(gè)UTF-8編碼的std::string,那么就需要將其轉(zhuǎn)換成UTF-16編碼的字符串std::wstring,Windows下std::filesystem::path能使用std::wstring對(duì)象進(jìn)行初始化。std::stringstd::wstring的相互轉(zhuǎn)換如下所示:

std::wstring Utf8StringToWideString(const std::string& utf8_str) {
  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
  return converter.from_bytes(utf8_str);
}
std::string WideStringToUtf8String(const std::wstring& wstr) {
  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
  return converter.to_bytes(wstr);
}

經(jīng)過筆者的驗(yàn)證,其實(shí)Windows環(huán)境下使用GBK編碼字符串初始化std::filesystem::path也可以。不過這不是重點(diǎn),重點(diǎn)是我很疑惑Windows環(huán)境下為什么不干脆統(tǒng)一使用UTF-8編碼初始化呢?本身標(biāo)準(zhǔn)庫的意義就在于統(tǒng)一不同系統(tǒng)環(huán)境下的行為,這里為了保證統(tǒng)一不得不又采用預(yù)編譯的辦法來跨平臺(tái),感覺這里標(biāo)準(zhǔn)庫白標(biāo)準(zhǔn)了,微軟真是不做人啊。

不過,雖然std::filesystem::path的初始化使用的字符編碼不統(tǒng)一,但是卻可以返回UTF-8編碼字符串,函數(shù)接口是u8string()。另外,generic_u8string()接口不僅可以返回UTF-8編碼字符串,而且所有路徑的目錄分隔符被轉(zhuǎn)換為正斜杠(/)。所以,筆者采用的策略是只要是路徑相關(guān)的字符串,一開始就初始化成std::filesystem::path,路徑相關(guān)的操作就局限在這個(gè)對(duì)象中進(jìn)行,從而避免考慮字符編碼的問題。并且,std::fstream也能接受std::filesystem::path作為參數(shù),使用起來還是很方便的。

3.2 Qt的QString

Qt的QString筆者認(rèn)為是最好的C++字符串實(shí)現(xiàn),字符編碼實(shí)現(xiàn)的非常不錯(cuò)。在代碼文件保存為UTF-8編碼,并且編譯器按照UTF-8編碼字符串的情況下,可以直接使用字符串字面量進(jìn)行初始化:

QString str = "這是中文字符串";

這是因?yàn)?code>"這是中文字符串"使用的是UTF-8編碼,這個(gè)字符串字面量會(huì)被正確地解釋為Unicode字符。接著當(dāng)構(gòu)造QString時(shí),它能夠自動(dòng)處理Unicode字符并將其轉(zhuǎn)換成內(nèi)部使用的 UTF-16編碼。

但是對(duì)于已經(jīng)存在的std::string或者其他形式的C風(fēng)格字符串,需要顯式指明其編碼格式,以確保QString能夠正確地解碼它們,例如:

std::string stdString = "一些UTF-8編碼的文本";
QString str = QString::fromUtf8(stdString.c_str());

這是因?yàn)?code>QString默認(rèn)假設(shè)傳入的C風(fēng)格字符串是以ISO 8859-1(Latin-1)編碼的。

3.3 GDAL

在統(tǒng)一使用UTF-8編碼之后,就不用再設(shè)置文件路徑的字符編碼不是UTF-8了,直接傳遞到GDALOpen函數(shù)中即可。

//CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");  
const char* imgPath = "E:\\Data\\lena.bmp";
GDALDataset* img = (GDALDataset *)GDALOpen(imgPath, GA_ReadOnly);

3.4 OpenCV

OpenCV的讀取圖像接口cv::imread使用的應(yīng)該是本地編碼,在Windows環(huán)境下需要進(jìn)行編碼轉(zhuǎn)換:

#ifdef _WIN32
      img = cv::imread(Utf8ToGbk(externalTexPath.u8string()), cv::IMREAD_UNCHANGED);
#else
      img = cv::imread(externalTexPath.u8string(), cv::IMREAD_UNCHANGED);
#endif

筆者之前的博文《c++中utf8字符串和gbk字符串的轉(zhuǎn)換》中提供了Utf8編碼與GBK編碼之間的轉(zhuǎn)換。

4. 補(bǔ)充

筆者查閱字符編碼相關(guān)的資料的時(shí)候,就感嘆這方面的知識(shí)還真就是一本爛賬,除非深入了解,否則是無法完全論述清楚的。個(gè)人看法是要認(rèn)清字符編碼的本質(zhì)是將有意義的字符與二進(jìn)制數(shù)據(jù)類型類型對(duì)應(yīng)起來。以國內(nèi)的情況來說,我們只需要理解三種字符編碼:ASCII、ANSI以及Unicode,它們大致分別對(duì)應(yīng)于1個(gè)字節(jié)、2個(gè)字節(jié)、以及4個(gè)字節(jié)。

  • ASCII編碼是原始編碼,包含大小寫英文字符+數(shù)字+標(biāo)點(diǎn)符號(hào)+控制字符+特殊字符,總共是128個(gè)。因此準(zhǔn)確來說ASCII編碼是7位字符編碼,但在高級(jí)語言中使用最小的數(shù)據(jù)類型就是1字節(jié)整型了。
  • ANSI編碼是本地編碼,在國內(nèi)的Windows環(huán)境中通常指國標(biāo)碼(國家標(biāo)準(zhǔn)標(biāo)碼),更加具體一點(diǎn)就是GB2312、GBK和GB18030這三種編碼。其中,GB2312編碼是第一版國標(biāo)碼,GBK編碼最常用,但是GB18030編碼是最新的。國標(biāo)碼最初被設(shè)計(jì)出來的時(shí)候,是2個(gè)字節(jié)對(duì)應(yīng)于1個(gè)字符,同時(shí)沒有占用ASCII編碼的內(nèi)容,因此是兼容ANSI編碼的。
  • Unicode編碼是國際編碼,它被設(shè)計(jì)出來的目的就是囊括并且統(tǒng)一世界上所有的字符,以此解決世界上不同本地編碼字符編碼轉(zhuǎn)換的問題。Unicode編碼最初被設(shè)計(jì)出來的時(shí)候,同樣是2個(gè)字節(jié)對(duì)應(yīng)于1個(gè)字符,這就是UTF-16編碼。但是字符的增加,Unicode編號(hào)很快不夠用了,就擴(kuò)展成了4字節(jié)對(duì)應(yīng)于1個(gè)字符,這就是UTF-32編碼。UTF-32編碼的問題就是太浪費(fèi)了,比如UTF-32編碼的前128位與ANSI編碼的編號(hào)是一樣的,但是卻要用4個(gè)字節(jié)表示,實(shí)際上與ANSI編碼一樣,同樣使用1個(gè)字節(jié)即可?;谶@樣的思想就誕生了UTF-8編碼,每個(gè)字符根據(jù)所分配的Unicode編號(hào)大小,使用1~4個(gè)字節(jié)來表示。
  • 那么原來2個(gè)字節(jié)的UTF-16編碼遇到超過2字節(jié)范圍的字符怎么辦呢?答案是使用2個(gè)連續(xù)的2個(gè)字節(jié)來進(jìn)行表示。UTF-16編碼的影響還是非常深遠(yuǎn)的,C#的string、Java的string、Qt的QString以及Win32 API普遍都使用UTF-16編碼。為了保證對(duì)4個(gè)字節(jié)字符的兼容,它們往往會(huì)采用“代理對(duì)”的技術(shù),由系統(tǒng)實(shí)現(xiàn)正常處理字符串長度、索引或其他涉及字符級(jí)別的操作。
  • UTF-8變長編碼的思想也影響了國標(biāo)碼的設(shè)計(jì),最新的國標(biāo)碼GB18030編碼也擴(kuò)展成為了變長編碼,并且兼容ASCII字符的單字節(jié)編碼,以及GB2312和GBK的雙字節(jié)編碼部分。
  • 本文中筆者不想將問題復(fù)雜化,特意沒有論述到UTF-8 BOM編碼的內(nèi)容。UTF-8 BOM編碼與UTF-8編碼是一樣的,只不過在字符內(nèi)容的部分加了幾個(gè)標(biāo)識(shí)符,從而可以讓編輯器知道該字符內(nèi)容是UTF-8編碼的。UTF-8 BOM編碼也是微軟搞出來的,主要是用來方便在本地編碼的環(huán)境中識(shí)別出UTF-8編碼。一般國際上更推薦統(tǒng)一使用標(biāo)準(zhǔn)的UTF-8編碼。

5. 參考

到此這篇關(guān)于C++代碼改造為UTF-8編碼問題的總結(jié)的文章就介紹到這了,更多相關(guān)C++ UTF-8編碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++中的友元函數(shù)與友元類詳情

    C++中的友元函數(shù)與友元類詳情

    這篇文章主要介紹了C++中的友元函數(shù)與友元類詳情,對(duì)類的封裝是C++三大特性中的一個(gè)重要特性,封裝好的數(shù)據(jù)在類的外部是訪問不到的但是一旦出了問題,想要操作被封裝的數(shù)據(jù)怎么辦呢?由此友元函數(shù)友元類誕生了,下文我們來詳細(xì)來接一下具體的有緣類吧
    2022-02-02
  • C++中拷貝構(gòu)造函數(shù)的使用

    C++中拷貝構(gòu)造函數(shù)的使用

    大家好,本篇文章主要講的是C++中拷貝構(gòu)造函數(shù)的使用,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-02-02
  • C++實(shí)現(xiàn)LeetCode(189.旋轉(zhuǎn)數(shù)組)

    C++實(shí)現(xiàn)LeetCode(189.旋轉(zhuǎn)數(shù)組)

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(189.旋轉(zhuǎn)數(shù)組),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C++11標(biāo)準(zhǔn)庫bind函數(shù)應(yīng)用教程

    C++11標(biāo)準(zhǔn)庫bind函數(shù)應(yīng)用教程

    bind函數(shù)定義在頭文件functional中,可以將bind函數(shù)看做成一個(gè)通用的函數(shù)適配器,他接收一個(gè)可調(diào)用對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來"適應(yīng)"原對(duì)象的參數(shù)列表。本文將帶大家詳細(xì)了解一下bind函數(shù)的應(yīng)用詳解
    2021-12-12
  • C++事件驅(qū)動(dòng)型銀行排隊(duì)模擬

    C++事件驅(qū)動(dòng)型銀行排隊(duì)模擬

    這篇文章主要為大家詳細(xì)介紹了C++事件驅(qū)動(dòng)型銀行排隊(duì)模擬,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 一篇文章帶你了解C語言:入門基礎(chǔ)(2)

    一篇文章帶你了解C語言:入門基礎(chǔ)(2)

    這篇文章主要介紹了C語言入門之基礎(chǔ)知識(shí)詳解,文中有非常詳細(xì)的C語言使用教程及相關(guān)基礎(chǔ)知識(shí),對(duì)正在學(xué)習(xí)c語言的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-08-08
  • c語言處理函數(shù)調(diào)用的方法

    c語言處理函數(shù)調(diào)用的方法

    函數(shù)就是一段封裝好的,可以重復(fù)使用的代碼,它使得我們的程序更加模塊化,不需要編寫大量重復(fù)的代碼。這篇文章主要介紹了c語言是如何處理函數(shù)調(diào)用的?需要的朋友可以參考下
    2021-11-11
  • C++函數(shù)pyrUp和pyrDown來實(shí)現(xiàn)圖像金字塔功能

    C++函數(shù)pyrUp和pyrDown來實(shí)現(xiàn)圖像金字塔功能

    這篇文章主要介紹了C++函數(shù)pyrUp和pyrDown來實(shí)現(xiàn)圖像金字塔功能,如何使用OpenCV函數(shù) pyrUp 和 pyrDown 對(duì)圖像進(jìn)行向上和向下采樣,需要的朋友可以參考下
    2017-03-03
  • C++動(dòng)態(tài)聯(lián)編介紹

    C++動(dòng)態(tài)聯(lián)編介紹

    這篇文章主要介紹了C++動(dòng)態(tài)聯(lián)編,在C++中,聯(lián)編是指一個(gè)計(jì)算機(jī)程序的不同部分彼此關(guān)聯(lián)的過程。按照聯(lián)編所進(jìn)行的階段不同,可分為兩種不同的聯(lián)編方法:靜態(tài)聯(lián)編和動(dòng)態(tài)聯(lián)編
    2022-01-01
  • C++函數(shù)模板與類模板實(shí)例解析

    C++函數(shù)模板與類模板實(shí)例解析

    這篇文章主要介紹了C++函數(shù)模板與類模板,需要的朋友可以參考下
    2014-08-08

最新評(píng)論