【C++】代码与输出编码格式详解

本文最后更新于:2023年3月14日 晚上 23:35

一篇深度好文全面解析C++编码格式问题!一站式解决C++输出乱码或者为空!

转载请注明原作者、原文地址。

结论预览

MSVC编译环境

源文件为GB2312/GBK编码格式

注:

  • 常量字符串指的是在Cpp文件中有双引号的字符串,形如string s = "常量字符串";
  • wstring的常量字符串前面需要添加'L'标记,注明为宽字符串wstring ws = L"常量宽字符串";
  • 下图中wcout(xxx)指的是设置wcout的locale,代码为wcout.imbue(locale("xxx"));
  • 下图中wcin/wcout(xxx)指的是wcin.imbue(locale("xxx"));wcout.imbue(locale("xxx")); 一般要同时设置wcin和wcout的locale,否则两者处理字符串的方式不同会造成难以预料的结果。(ps.其实也是懒得去实验了,但说不定有什么妙用)
类型 IO流 控制台代码页 原始保存效果/输入输出效果 中文输出效果 补救方法
string常量字符串 cout 936 以GB2312/GBK编码保存,输出无需转码。 正常 \
65001 以GB2312/GBK编码保存,输出时不会隐式转码。 乱码 使用ANSI_to_UTF8函数处理后再使用cout输出。
string变量 cin/cout 936 将控制台中的GBK字符串以GB2312/GBK编码保存,输出无需转码。 正常 \
65001 将控制台中的UTF-8字符串以GB2312/GBK编码保存,输出时不会隐式转码。 乱码 使用ANSI_to_UTF8函数处理后再使用cout输出。
wstring常量字符串 wcout(默认) 936 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(chs) 936 以UTF-16编码保存,输出时将UTF-16转为ANSI。 正常 \
65001 以UTF-16编码保存,输出时将UTF-16转为ANSI导致乱码。 乱码 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(gbk) 936 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(zh_CN.utf8) 936 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8导致乱码。 乱码 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8。 正常 \
wstring变量 wcin/wcout(默认) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(chs) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时将UTF-16转为ANSI。 正常 \
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时将UTF-16转为ANSI导致乱码。 乱码 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(gbk) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(zh_CN.utf8) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8导致乱码。 乱码 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8。但似乎wcin无法正确处理UTF-8输入,类似于给int输入字符串的错误。 有的显示的是之前保存的常量字符串,有的显示乱码比如“十七”,如果原先输入的开头为ascll ,则后续中文变为P或乱码 使用cin输入到string,再使用UTF8_to_UTF16函数进行转码后保存到wstring。输出时使用UTF16_to_UTF8函数处理后再使用cout输出。

源文件为UTF-8编码格式

注意:

  • MSVC不支持在UTF8的Cpp文件内为wstring赋值,无法通过编译。比如wstring ws = L"是当前文件";
  • 在GB2312/GBK的Cpp文件中该语句可正常通过编译。
类型 IO流 控制台代码页 原始保存效果/输入输出效果 中文输出效果 补救方法
string常量字符串 cout 936 以GB2312/GBK编码保存,输出无需转码。 正常 \
65001 以GB2312/GBK编码保存,输出时不会隐式转码。 乱码 使用ANSI_to_UTF8函数处理后再使用cout输出。
string变量 cin/cout 936 将控制台中的GBK字符串以GB2312/GBK编码保存,输出无需转码。 正常 \
65001 将控制台中的UTF-8字符串以GB2312/GBK编码保存,输出时不会隐式转码。 乱码 使用ANSI_to_UTF8函数处理后再使用cout输出。
wstring常量字符串 wcout(默认) 936 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(chs) 936 以UTF-16编码保存,输出时将UTF-16转为ANSI。 正常 \
65001 以UTF-16编码保存,输出时将UTF-16转为ANSI导致乱码。 乱码 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(gbk) 936 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcout(zh_CN.utf8) 936 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8导致乱码。 乱码 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8。 正常 \
wstring变量 wcin/wcout(默认) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(chs) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时将UTF-16转为ANSI。 正常 \
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时将UTF-16转为ANSI导致乱码。 乱码 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(gbk) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 将控制台中的UTF-8字符串以UTF-16编码保存,输出时不会隐式转码。 使用UTF16_to_UTF8函数处理后再使用cout输出。
wcin/wcout(zh_CN.utf8) 936 将控制台中的GBK字符串以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8导致乱码。 乱码 使用UTF16_to_ANSI函数处理后再使用cout输出。
65001 以UTF-16编码保存,输出时会隐式转码将UTF-16转为UTF-8。但似乎wcin无法正确处理UTF-8输入,类似于给int输入字符串的错误。 有的显示的是之前保存的常量字符串,有的显示乱码比如“十七”,如果原先输入的开头为ascll ,则后续中文变为P或乱码 使用cin输入到string,再使用UTF8_to_UTF16函数进行转码后保存到wstring。输出时使用UTF16_to_UTF8函数处理后再使用cout输出。

GCC编译环境

字符编码

ASCLL

字符编码指的是将字符集中的字符转换为计算机可识别的二进制编码的方式。常见的字符编码有ASCII、Unicode、UTF-8、UTF-16等。

  • ASCII编码是最早的文本编码格式,只能表示128个字符,包括英文字母、数字和一些符号。

Unicode

  • Unicode编码则支持全球范围内的字符集,包括中文、日文、韩文等。Unicode作为字符集,定义了各种语言中的字符及其对应的唯一编码。而UTF-8和UTF-16则是Unicode的具体编码方式,它们将Unicode字符集中的字符转换成计算机可以识别的二进制编码。
  • UTF-8编码则是一种可变长度的Unicode编码,它能够兼容ASCII编码,同时也支持全球范围内的字符集。UTF-8编码方式采用1-4个字节来表示一个字符,具体采用几个字节来表示一个字符取决于这个字符的Unicode编码。
  • UTF-16是另外一种Unicode编码方式,它用16位来表示一个字符。与UTF-8不同,UTF-16是固定长度的,每个字符都是16位(2个字节),这使得UTF-16在处理特殊字符时比UTF-8更高效。UTF-16编码方式适用于需要支持大量字符集的应用程序,如多语言网站、多语言文本编辑器等。但是,UTF-16也存在一些缺点,比如它需要更多的存储空间,因为每个字符都用16位来表示,而且它不支持变长编码,因此在处理ASCII字符时会浪费存储空间。

ANSI

ANSI(American National Standards Institute)是美国国家标准化组织,成立于1918年。它制定了许多标准,包括计算机领域的标准。在计算机领域中,ANSI制定了一系列编码标准,如ASCII编码标准等。

在Windows操作系统中,ANSI通常指代一种字符编码方式,即Windows代码页(Windows Code Page),也称为ANSI代码页。Windows代码页是一种与语言和地区有关的字符编码方式,它用于将字符集中的字符转换为计算机可识别的二进制编码。Windows操作系统中的ANSI字符集包括许多不同的代码页,如中文的GB2312、BIG5、日文的Shift-JIS等,这些代码页都是基于ASCII字符集的扩展,用于支持不同语言和地区的字符集。但是,ANSI字符集存在一些缺点,如无法支持多语言字符集,容易出现字符集兼容性问题等,因此现在已经逐渐被Unicode编码所取代。

  • GB2312:GB2312是中国国家标准,于1980年发布,它采用双字节编码方式,其中第一个字节为高字节,第二个字节为低字节。包含了近7,000个中文字符和符号,其中包括了基本汉字、简化字、繁体字以及一些常用的非汉字字符。
  • GBK:GBK是GB2312的扩展编码方式,它于1995年发布,也采用双字节编码方式,其中第一个字节为高字节,第二个字节为低字节。与GB2312不同的是,GBK字符集包含了超过21,000个中文字符和符号,包括了繁体字、日韩汉字以及一些生僻字等。GBK编码方式在中国大陆和台湾地区广泛使用,它支持GB2312字符集,因此可以兼容GB2312编码方式,同时也支持更多的中文字符和符号。

C++与窄字符和宽字符

窄字符

  • char 类型:保存一个字节。
  • string / char[] 类型:以单个字节为单位保存一个字符序列。
  • cout / cin IO流:
  • ofstream / ifstream 文件流:

宽字符

  • wchat_t 类型:保存两个字节。
  • wstring / wchar_t[] 类型:以两个字节为单位保存字符序列。
  • wcout / wcin :
  • wofstream / wifstream :

控制台代码页

默认情况下中国地区的控制台代码页为 936(GBK)。输入 chcp 并回车可以查看当前活动代码页。

image-20230313185709825

输入 chcp 65001 回车后会切换到65001代码页(UTF-8)。当然,这种更改是局部的,只要退出这个控制台,代码页依然为936。

image-20230313185945473

C++控制台程序输出乱码的原因

影响字符输出效果的因素有三个:

  1. cpp/h/hpp文件编码格式
  2. 在程序内部是否人为使用了编码转换
  3. 控制台代码页

代码文件格式

主要影响的是在代码里面写的常量字符串,比如 string s = "你好"; wstring ws = L"你好";

人为编码转换

人为使用了<codecvt>头文件的编码转换(比如里面的 UTF-8 转 UTF-16 的函数 codecvt_utf8_utf16),或者自己通过位运算、字节的操控(windows.h头文件中提供的WideCharToMultiByte、MultiByteToWideChar函数)。

控制台代码页

如前文中936为GBK编码,65001为UTF-8编码,这直接决定了控制台会如何处理程序向控制台输出的字符序列!以及控制台向程序输入的字符序列编码格式!

如何保证不输出乱码或空字符?

(一)12轮测试

1. Cpp/H/Hpp文件保存为UTF-8格式

可在自己的IDE中进行选择。(有的IDE没有这种功能)

2. 控制台代码页修改为65001

1
system("chcp 65001");

3. 设置wcout/wcin的locale

具体代码如下:

1
2
3
wcout.imbue(locale("zh_CN.UTF-8"));	// "zh_CN.UTF-8"也可换为"chs"
wcin.imbue(locale("zh_CN.UTF-8"));
// wofstream、wifstream 同理

这里我遇到了一个问题,在我的VSCode和Sublime上使用TDM-GCC作为编译器,编译器一直报错,报错信息入下:

1
2
terminate called after throwing an instance of 'std::runtime_error'
what(): locale::facet::_S_create_c_locale name not valid

然后我换成了CodeBlocks(Mingw-64 GNU GCC)和Visual Studio(MSVC)进行编译,没有任何报错。折腾了半天也没搞定VSCode和Sublime上的问题, 也许是TDM-GCC的BUG吧,只好作罢。接下来我使用CodeBlocks和VS进行测试。

测试1:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:不设置
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

VS通过编译,wcout/wcin能输出代码内部的宽字符串,但对于用户输入的ascll和宽字符没有反应。

CodeBlocks编译器报错。

初步判断为wcout/wcin的锅,它们无法接受单字节的字符。

image-20230313212052031

测试2:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

VS通过编译,wcout能输出代码内部的宽字符串,也能接受用户输入,是比较理想的效果。

CodeBlocks编译器报错。

此时为GB2312环境下MSVC的最优解,GCC编译器除外。

image-20230313212457722

测试3:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:设置为GBK
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
wcout.imbue(locale("GBK"));
wcin.imbue(locale("GBK"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

VS通过编译,wcout/wcin能输出代码内部的宽字符串,但对于用户输入的宽字符则没有反应。

CodeBlocks编译器报错。

初步判断为中文字符集不太兼容的问题,具体原因未知。

image-20230313212816133

测试4:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:不设置
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

VS通过编译,cout/cin输出ASCLL正常,中文输出乱码,wcout/wcin能输出代码内部的ASCLL,但对于用户输入的则没有反应。

CodeBlocks编译器报错。

初步判断为控制台对字符流的处理问题以及wcout/wcin内部问题。

image-20230313212924855

测试5:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

VS通过编译,除ASCLL以外输出为乱码。

CodeBlocks编译器报错。

初步判断为GBK与UTF-8不兼容的问题。

image-20230313213005587

测试6:

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:设置为GBK
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
wcout.imbue(locale("GBK"));
wcin.imbue(locale("GBK"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313213046539

测试7:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:不设置
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行!

注意到一点,这里VS的控制台居然自动变为了65001代码页,有点奇怪。

image-20230313213424518

测试8:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313213813461

测试9:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:设置为zh_CN.utf8
  3. 控制台代码页:默认936(GBK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
wcout.imbue(locale("zh_CN.utf8"));
wcin.imbue(locale("zh_CN.utf8"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313213945450

测试10:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:不设置
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313214212088

测试11:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313214721673

测试12:

  1. 文件保存格式:UTF-8
  2. wcout/wcin设置locale:设置为zh_CN.utf8
  3. 控制台代码页:65001
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <locale>
#include <string>
using namespace std;
int main() {
system("chcp 65001");
wcout.imbue(locale("zh_CN.utf8"));
wcin.imbue(locale("zh_CN.utf8"));
// 英文测试
string outstr = "aaa", instr;
wstring outwstr = L"bbb", inwstr;
cout << outstr << endl;
wcout << outwstr << endl;
// 中文测试
outstr = "梦梦";
outwstr = L"娜娜";
cout << outstr << endl;
wcout << outwstr << endl;
// 英文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
// 中文测试
cin >> instr;
wcin >> inwstr;
cout << instr << endl;
wcout << inwstr << endl;
}

简要:不行

image-20230313215538438

(二)从中挑选出最佳方案

给出四个转码函数。其中前两个需要添加头文件<codecvt>,后两个要添加头文件<Windows.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
wstring UTF8_to_UTF16(const string& strUTF8) {
#if __cplusplus >= 201703L && defined(__cpp_lib_char8_t) // 检查编译器是否支持 C++17 和 char8_t
std::wstring_convert<std::codecvt_char8<wchar_t>, wchar_t> converter;
std::wstring strUTF16 = converter.from_bytes(reinterpret_cast<const char*>(strUTF8.data()), strUTF8.data() + strUTF8.size());
#else
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring strUTF16 = converter.from_bytes(strUTF8);
#endif
return strUTF16;
}

string UTF16_to_UTF8(const wstring& strUTF16) {
#if __cplusplus >= 201703L // 判断编译器是否支持 C++17,因为C++17改了,与之前的版本冲突
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
#else
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
#endif
std::string strUTF8 = converter.to_bytes(strUTF16);
return strUTF8;
}

wstring ANSI_to_UTF16(const string& strANSI) {
int nWide = ::MultiByteToWideChar(CP_ACP, 0, strANSI.c_str(), (int)strANSI.size(), NULL, 0);
unique_ptr<wchar_t[]> buffer(new wchar_t[nWide + 1]);
if (!buffer)
return L"";
::MultiByteToWideChar(CP_ACP, 0, strANSI.c_str(), (int)strANSI.size(), buffer.get(), nWide);
buffer[nWide] = L'\0';
return buffer.get();
}

string UTF16_to_ANSI(const wstring& strUTF16) {
int nAnsi = ::WideCharToMultiByte(CP_ACP, 0, strUTF16.c_str(), (int)strUTF16.size(), NULL, 0, NULL, NULL);
unique_ptr<char[]> buffer(new char[nAnsi + 1]);
if (!buffer)
return "";
::WideCharToMultiByte(CP_ACP, 0, strUTF16.c_str(), (int)strUTF16.size(), buffer.get(), nAnsi, NULL, NULL);
buffer[nAnsi] = '\0';
return buffer.get();
}

方案一(不可用于GCC):

经过在VS上的测试,此情况使用wcin输入的宽字符串为UTF-16编码格式,当需要输出UTF-8时,则必须将其用UTF16_to_UTF8函数转换后输出。

当ANSI想要输出UTF-8时,可以使用ANSI_to_UTF16和UTF16_to_UTF8两个函数进行转换。

  1. 文件保存格式:GB2312
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:默认936(GBK)
  4. codecvt头文件:输出utf-8文件时使用
1
2
3
4
5
6
7
8
9
#include <iostream>
#include <locale>
#include <string>
#include <codecvt>
using namespace std;
int main() {
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
}

方案二(VS特化方案)(推荐):

  1. 文件保存格式:(VS中的)Unicode
  2. wcout/wcin设置locale:设置为chs
  3. 控制台代码页:默认936(GBK)
  4. codecvt头文件:输出utf-8文件时使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <locale>
#include <string>
#include <codecvt>
using namespace std;
string UTF16_to_UTF8(const wstring& strUTF16) {
#if __cplusplus >= 201703L // 判断编译器是否支持 C++17,因为C++17改了,与之前的版本冲突
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
#else
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
#endif
std::string strUTF8 = converter.to_bytes(strUTF16);
return strUTF8;
}
int main() {
wcout.imbue(locale("chs"));
wcin.imbue(locale("chs"));
wstring ws;
wcin >> ws;
wcout << ws;
ofstream wofs("sss.txt", ios::out | ios::trunc);
wofs.imbue(locale("chs"));
wofs << UTF16_to_UTF8(ws);
}

这样将可以实现输出UTF-8文本。同时其他功能一切正常!(VS yyds!)

注:必须使用C++14及以下版本!!

方案三(VS特化方案):

  1. 文件保存格式:(VS中的)Unicode
  2. wcout/wcin设置locale:不用设置
  3. 控制台代码页:默认936(GBK)
  4. codecvt头文件:输出utf-8文件时使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <locale>
#include <string>
#include <codecvt>
using namespace std;
string UTF16_to_UTF8(const wstring& strUTF16) {
#if __cplusplus >= 201703L // 判断编译器是否支持 C++17,因为C++17改了,与之前的版本冲突
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
#else
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
#endif
std::string strUTF8 = converter.to_bytes(strUTF16);
return strUTF8;
}
wstring ANSI_to_UTF16(const string& strANSI) {
int nWide = ::MultiByteToWideChar(CP_ACP, 0, strANSI.c_str(), (int)strANSI.size(), NULL, 0);
unique_ptr<wchar_t[]> buffer(new wchar_t[nWide + 1]);
if (!buffer)
return L"";
::MultiByteToWideChar(CP_ACP, 0, strANSI.c_str(), (int)strANSI.size(), buffer.get(), nWide);
buffer[nWide] = L'\0';
return buffer.get();
}
int main() {
string s;
cin >> s;
cout << s;
ofstream wofs("sss.txt", ios::out | ios::trunc);
wofs << UTF16_to_UTF8(ANSI_to_UTF16(s));
}

这样将可以实现输出UTF-8文本。同时其他功能一切正常!(VS yyds!)

注:必须使用C++14及以下版本!!


【C++】代码与输出编码格式详解
https://qalxry.github.io/2023/03/14/【C++】代码与输出编码格式详解/
作者
しずり雪
发布于
2023年3月14日
更新于
2023年3月14日
许可协议