c++中的字符串编码处理方法
今天由于在项目中用到一些与c++混合开发的东西 ,需要通过socket与c++那边交换数据,没啥特别的,字节码而已,两边确定一种编码规则就行了。我们确定的utf-8。关于c++的 这种又是宽字节 又是messageboxw 又是messageboxa 的 ,说实话相比c#而言 搞的确实非常的和稀泥 搞的非常的糊,别说新手 有些不是新手的都搞不明白。
字符串字面量怎么被编码成字节的
什么是字符串?c#里的 string?c++里的char* ? 字符串的本质是什么?字符串不过是一个特殊的数据字节包装 带有编码信息,特别是c++的 更原始 更便于我们想清楚这个底层,其实其他的已经迎刃而解了。首先我们无论如何确定一个东西 那就是交换的东西是字节码 ,说白了 也就是c++ 里的char [ ] 也就是char *,在我不管你编码的情况下 我新建vc++项目 在代码里这样写:
char str1[] = "中a"; printf("%s\r\n", str1);
能不能输出东西?能不能输出中文 当然能,那这个str1 字节码到底是什么字节码, 只要我们把这个搞明白就可以了。一切未知的恐惧源于不明白。我们先调试c++代码 取到字节码,然后编写下面这两句c#代码:
byte[] bts2 = new byte[] { 0xd6, 0xd0, 0x61 }; 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#进行验证:
byte[] bts3 = new byte[] { 0x2d,0x4e, 0x61,0x00, }; 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:
void unicodetoutf8(const wchar_t* unicode,char utf82[],int * lenout) { int len; len = widechartomultibyte(cp_utf8, 0, unicode, -1, null, 0, null, null); char szutf82[50] = { 0 }; *lenout = len; widechartomultibyte(cp_utf8, 0, unicode, -1, utf82, len, null, null); }
关于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++里这些概念搞的比较糊 ,我描述的这些也是个意会 ,也许某些细节部分说错了 像原来文章里那些评论里那样 尖锐的指出来 不怕批评。
最后 ,一些测试的大杂烩代码:
// consoleapplication1.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include "h1.h" #include "fqtabdata.h" #include "test1.h" #include <windows.h> #include <string> #include <iomanip> #include <type_traits> using namespace std; //引用的使用方式 void test1(int &r){ r = r+1; } void unicodetoutf8(const wchar_t* unicode,char utf82[],int * lenout) { int len; len = widechartomultibyte(cp_utf8, 0, unicode, -1, null, 0, null, null); char szutf82[50] = { 0 }; *lenout = len; widechartomultibyte(cp_utf8, 0, unicode, -1, utf82, len, null, null); } int _tmain(int argc, _tchar* argv[]) { setlocale(lc_all, "");//注意控制台输出要先加上这句哈要不然无法输出中文 wchar_t wstr2[] = l"中a"; wprintf(l"%ls\r\n", wstr2); char str1[] = "中ab"; printf("%s\r\n", str1); return 0; //关于c++里的编码问题 // 并非 不在在项目属性里设置编码字符集 为unicode 就不能显示中文 //char str11[] = "中a"; printf("%s", str11); //这段代码照样显示中文,中a被编译器编成3个元素存在str11 里+\0结尾 //当选择“使用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); //关于这个l ,等同于_t("") tchar 这些玩意儿他们都有同等意义 //可以傻瓜的理解 l 本身就是搞一个宽字符型 字符串 ,每个字符占2字节 //wchar_t ws[] = l"国家"; //设置为unicode 就意味着宽字符 就意味着字符串 要加l //就像前面的 好多函数接口有两种版本 messageboxa messageboxw , //messageboxw就意味着你要传一个宽字符数组进去 也就是 wchar_t 或者l"dd" //注意多字节字符集是一个很容易让人费解的玩意儿, //我们说 utf-8是 一种unicode的落地编码 //编程里都是用 unicode 不管项目设没设置unicode字符集 wchar_t ws[] = l"国家"; 得到的都是宽字符串 //但是编程代码里 没有utf-8 这一说法 utf-8是变长的 也就是多字节 他是一种编码落地 //你想想你整个变长 别人接口怎么写 ,怎么达到在让你用变长省内存的同时 识别你的有效字符 //如果数组里存utf-8 你想想 别人要以字节数读字符 半个的时候怎么搞 //这跟gdi图像处理是同一个道理 jpg png 各种是落地格式都可以读进来 但是到内存都是bmp //还有不论哪种printf 或者其他接口 都不支持所谓的utf-8的参数 也没这种接口可言 //https://zhuanlan.zhihu.com/p/23190549 //前几天在微博上受到了@belleve给我的启发,于是简单地实现了几个在 windows //下接受 utf - 8 参数的 printf 系列函数。大致思路是判断当前 stdout / stderr //是否为控制台,如果是控制台则将参数转为 utf - 16 后调用 wprintf 输出,否则不转换直接调用 printf。 //l 是一个很微妙的 ,称之为转换为宽字符的字面量 什么叫字面量 根据你当前编程环境 以及源代码编码 转换成对应的字节 //l"发" 字面量 你细品 setlocale(lc_all, ""); printf("--------------------"); //wchar_t wc = l'破'; std::wstring wstr = l"破a的"; std::cout << wstr.size() << std::endl; //utf-8 只是流行 ,事实上utf-8 一个汉字要占3字节 而utf-16一个汉字一字节 /*wchar_t wstr2[] = l"破晓s"; wprintf(l"%ls", wstr2);*/ printf("--------------------//"); char utf82[50] = { 0 }; int len = 0; unicodetoutf8(wstr2, utf82, &len); //char* str222 = unicodetoutf8(wstr2); //printf("%s", str222); //printf("aaa"); return 0; // //c++ 中指针的变种 引用的使用方式 printf("aaa\r\n"); int a = 123; int& b = a; a = 456; printf("%d \r\n", b); test1(b); printf("%d \r\n", b); int c = 345; test1(c); printf("%d \r\n", c); return 0; }