C++中的字串編碼處理

2023-05-15 06:01:13

今天由於在專案中用到一些與C++混合開發的東西 ,需要通過socket與C++那邊交換資料,沒啥特別的,位元組碼而已,兩邊確定一種編碼規則就行了。我們確定的UTF-8。關於C++的 這種又是寬位元組 又是MessageBoxW 又是MessageBoxA 的 ,說實話相比C#而言 搞的確實非常的和稀泥 搞的非常的糊,別說新手 有些不是新手的都搞不明白。

字串字面量怎麼被編碼成位元組的

什麼是字串?C#裡的 string?C++裡的char* ? 字串的本質是什麼?字串不過是一個特殊的資料位元組包裝 帶有編碼資訊,特別是C++的 更原始 更便於我們想清楚這個底層,其實其他的已經迎刃而解了。首先我們無論如何確定一個東西  那就是交換的東西是位元組碼 ,說白了 也就是C++ 裡的char [ ]  也就是char *,在我不管你編碼的情況下 我新建VC++專案 在程式碼裡這樣寫:

1 char str1[] = "中a";
2 printf("%s\r\n", str1);

能不能輸出東西?能不能輸出中文 當然能,那這個str1 位元組碼到底是什麼位元組碼, 只要我們把這個搞明白就可以了。一切未知的恐懼源於不明白。我們先偵錯C++程式碼 取到位元組碼,然後編寫下面這兩句C#程式碼:

1 byte[] bts2 = new byte[] { 0xd6, 0xd0, 0x61 };
2 Console.WriteLine(Encoding.GetEncoding("gb2312").GetString(bts2));

正常輸出了C++程式碼裡的中文 由此可見C++裡預設程式碼到位元組  的字面量轉換 就是gb2312 ,就這樣而已。就這樣而已 ,真的就這麼點東西 ,不要探究是什麼機制驅使VC++預設把字串轉換到了gb2312編碼,事情不要歪呀歪的想想複雜了,人的精力是有限的 要放在有作用的地方。你看C++裡是char [ ] 還不像C#的string經過包裝的 更便於你想明白這個過程。不是說C++有std庫麼 不是有string 麼 還沒講呢 ,C++這門語言呢又好又不好 設計特點是暴露的細節多 各個細節你都可以自己控制 讓會用的人知道自己在做什麼 ,但是也有些坑,其實string 就是char[] 的變種而已。你看C++裡 在你琢磨不透的情況下悄然在你不知情編碼的情況下轉換成了位元組碼,C#的string 封裝的 不會給你這個機會 有明確的Encoding庫呼叫指定編碼。

窄字元和寬字元 ,怎麼個寬法

C++裡字串的字面量分為兩種 一種是普通的窄字元 ,也就是普通的char [ ] 一個元素佔1位元組, 另一種是寬字元 wchar_t [ ] 一個元素佔2位元組,_T("中a") 或者L"中a" 這種就是強行表示Unicode寬字元字面量。 寬字元 怎麼個寬法呢,我們說他是Unicode 也就是utf-16,我們用C#進行驗證:

1 byte[] bts3 = new byte[] {  0x2d,0x4e, 0x61,0x00, };
2 Console.WriteLine(Encoding.Unicode.GetString(bts3));

 好了,這就明朗了,C++這玩意兒 由於歷史遺留原因,直接在程式碼書寫字串字面量搞了兩套標準 窄字元和寬字元 ,你看上面的同字元裡面的位元組碼整的兩套標準  這就很扯,整的被迫大多數C++的函數 或者介面都要按照這個套路玩。就有了看到的MessageBoxA ()接受char[]窄字元引數,MessageBoxW()接受寬字元引數 ,不要有誤區哈 覺得char[ ] 就不能輸出中文 ,能不能是由對應的地方能不能解析這個位元組碼決定的 而不是其他。

關於UTF-8

utf-8的現實意義更大於程式設計的字面量意義 ,為什麼這麼說,現在網路 資料交換都是UTF-8 編碼,C++程式設計 字面量 沒有所謂UTF-8這個說法 ,UTF-8是一種落地編碼,落地編碼 懂嗎?就像影象程式設計  儲存最終格式有.jpg .png,utf-8 他是變長的 對於字串處理會出現很多問題 不利於程式處理,影象程式設計中不管你jpg png格式也好載入到記憶體中最後都是易於處理的BMP記憶體映像。程式設計中都是Unicode因為2位元組代表一個字元 標標準準的 是對齊的,利於程式設計處理。還有 utf-8 一箇中文3位元組 其實比utf-16 一箇中文2位元組 多, 但是如果是英文的話 就是1位元組 可以實現Unicode到ASCII的無縫轉換 可以處理一些老舊系統的相容問題。 C++裡Unicode可以通過手段轉換為UTF-8:

1 void UnicodeToUtf8(const wchar_t* unicode,char  utf82[],int * lenout)
2 {
3     int len;
4     len = WideCharToMultiByte(CP_UTF8, 0, unicode, -1, NULL, 0, NULL, NULL);
5     char szUtf82[50] = { 0 };
6     *lenout = len;
7     WideCharToMultiByte(CP_UTF8, 0, unicode, -1, utf82, len, NULL, NULL);
8     
9 }

 

關於VC++專案屬性裡的設定字元集

什麼意思呢:

 當選擇「使用Unicode字元集」時,編譯器會增加宏定義——UNICODE;而選擇「使用多位元組字元集」時,編譯器則不會增加宏定義——UNICODE。https://blog.csdn.net/huashuolin001/article/details/95620424
當選用「使用Unicode字元集」時,呼叫函數MessageBox,實際使用的是MessageBoxW,MessageBoxW關於字串的入參型別是LPCWSTR,使用MessageBox時,字串前需加L::MessageBox(NULL, L"這是一個測試程式!", L"Title", MB_OK);

多位元組,預設的窄字元char[]帶中文 就是典型的多位元組,接上面章節說明 多位元組+中文 對於字串處理分割 會帶來很多問題,所以帶中文請儘量使用寬字元。然後另一個 基於gb2312和Unicode編碼我就不細說了哈,如果你想你的程式能夠賣到國外在世界範圍內使用,那麼請使用Unicode,也就是 L" " 寬字元。C++裡這些概念搞的比較糊 ,我描述的這些也是個意會 ,也許某些細節部分說錯了 像原來文章裡那些評論裡那樣 尖銳的指出來  不怕批評。

 最後 ,一些測試的大雜燴程式碼:

  1 // ConsoleApplication1.cpp : 定義控制檯應用程式的入口點。
  2 //
  3 
  4 #include "stdafx.h"
  5 #include <iostream>
  6 #include "h1.h"
  7 #include "FqTabData.h"
  8 #include "test1.h"
  9 
 10 #include <windows.h>
 11 #include <string>
 12 #include <iomanip>
 13 #include <type_traits>
 14 
 15 using namespace std;
 16 
 17 
 18 //參照的使用方式
 19 void test1(int &r){
 20     r = r+1;
 21 }
 22 
 23 void UnicodeToUtf8(const wchar_t* unicode,char  utf82[],int * lenout)
 24 {
 25     int len;
 26     len = WideCharToMultiByte(CP_UTF8, 0, unicode, -1, NULL, 0, NULL, NULL);
 27     char szUtf82[50] = { 0 };
 28     *lenout = len;
 29     WideCharToMultiByte(CP_UTF8, 0, unicode, -1, utf82, len, NULL, NULL);
 30     
 31 }
 32 int _tmain(int argc, _TCHAR* argv[])
 33 {
 34     
 35     setlocale(LC_ALL, "");//注意控制檯輸出要先加上這句哈要不然無法輸出中文
 36     wchar_t wstr2[] = L"中a";
 37     wprintf(L"%ls\r\n", wstr2);
 38 
 39     char str1[] = "中ab";
 40     printf("%s\r\n", str1);
 41     return 0;
 42     //關於c++裡的編碼問題
 43     //    並非 不在在專案屬性裡設定編碼字元集 為Unicode 就不能顯示中文
 44     //char str11[] = "中a";         printf("%s", str11);
 45     //這段程式碼照樣顯示中文,中a被編譯器編成3個元素存在str11 裡+\0結尾
 46     //當選擇「使用Unicode字元集」時,編譯器會增加宏定義——UNICODE;而選擇「使用多位元組字元集」時,編譯器則不會增加宏定義——UNICODE。
 47     //https://blog.csdn.net/huashuolin001/article/details/95620424
 48     //當選用「使用Unicode字元集」時,呼叫函數MessageBox,實際使用的是MessageBoxW,MessageBoxW關於字串的入參型別是LPCWSTR,
 49     //使用MessageBox時,字串前需加L
 50     //::MessageBox(NULL, L"這是一個測試程式!", L"Title", MB_OK);
 51 
 52     //關於這個L ,等同於_T("")  Tchar 這些玩意兒他們都有同等意義
 53     //可以傻瓜的理解 L 本身就是搞一個寬字元型 字串 ,每個字元佔2位元組
 54     //wchar_t ws[] = L"國家";
 55     //設定為Unicode 就意味著寬字元 就意味著字串 要加L
 56     //就像前面的 好多函數介面有兩種版本 MessageBoxA MessageBoxW ,
 57     //MessageBoxW就意味著你要傳一個寬字元陣列進去 也就是 wchar_t 或者L"dd"
 58 
 59     //注意多位元組字元集是一個很容易讓人費解的玩意兒,
 60     //我們說  utf-8是 一種Unicode的落地編碼
 61     //程式設計裡都是用 Unicode 不管專案設沒設定Unicode字元集 wchar_t ws[] = L"國家"; 得到的都是寬字串
 62     //但是程式設計程式碼裡 沒有utf-8 這一說法 utf-8是變長的 也就是多位元組   他是一種編碼落地
 63     //你想想你整個變長 別人介面怎麼寫 ,怎麼達到在讓你用變長省記憶體的同時 識別你的有效字元
 64     //如果陣列裡存utf-8 你想想 別人要以位元組數讀字元 半個的時候怎麼搞
 65     //這跟gdi影象處理是同一個道理 jpg png 各種是落地格式都可以讀進來 但是到記憶體都是bmp
 66 
 67     //還有不論哪種printf 或者其他介面 都不支援所謂的utf-8的引數 也沒這種介面可言
 68     //https://zhuanlan.zhihu.com/p/23190549
 69     //前幾天在微博上受到了@Belleve給我的啟發,於是簡單地實現了幾個在 Windows 
 70     //下接受 UTF - 8 引數的 printf 系列函數。大致思路是判斷當前 stdout / stderr
 71     //是否為控制檯,如果是控制檯則將引數轉為 UTF - 16 後呼叫 wprintf 輸出,否則不轉換直接呼叫 printf。
 72 
 73     //L 是一個很微妙的 ,稱之為轉換為寬字元的字面量  什麼叫字面量 根據你當前程式設計環境 以及原始碼編碼 轉換成對應的位元組 
 74     //L"發" 字面量 你細品
 75     setlocale(LC_ALL, "");
 76 
 77 
 78 
 79 
 80     printf("--------------------");
 81     //wchar_t wc = L'破';
 82     std::wstring wstr = L"破a的";
 83     std::cout << wstr.size() << std::endl;
 84     //utf-8 只是流行 ,事實上utf-8 一個漢字要佔3位元組  而utf-16一個漢字一位元組
 85     /*wchar_t wstr2[] = L"破曉S";
 86     wprintf(L"%ls", wstr2);*/
 87     printf("--------------------//");
 88 
 89     char utf82[50] = { 0 };
 90     int len = 0;
 91     UnicodeToUtf8(wstr2, utf82, &len);
 92     //char* str222 = UnicodeToUtf8(wstr2);
 93     //printf("%S", str222);
 94     //printf("aaa");
 95     return 0;
 96     //
 97     //c++ 中指標的變種  參照的使用方式
 98     printf("aaa\r\n");
 99 
100     int a = 123;
101     int& b = a;
102     a = 456;
103     printf("%d \r\n", b);
104 
105     test1(b);
106     printf("%d \r\n", b);
107 
108     int c = 345;
109     test1(c);
110     printf("%d \r\n", c);
111     return 0;
112 }