![966b6d63086eefc6fe5e793933699233.png](https://img-blog.csdnimg.cn/img_convert/966b6d63086eefc6fe5e793933699233.png)
C语言如何输出中文?
测试前提:1. 源代码为UTF8编码的文件。2. 中文Windows系统。
测试环境:Windows 10,MinGW-w64 8.1。
如果用Windows SDK,效果可能和我的测试有极大不同,可能可以用UTF8,具体见评论区。
你好
的编码信息
- GBK: C4E3 BAC3
- UTF8: E4BDA0 E5A5BD
- UTF16 LE:4F60 597D
char硬编码字符串
#include <stdio.h>
#include <stdlib.h>
void ShowBytes(char *str)
{
if (*str == '0')
printf("EMPTY");
while (*str != '0')
printf("%hhX ", (unsigned char)*str++);
// 中文的char的最高位为1,如果直接赋值给int或unsigned,会先进行位扩展,再改变类型,结果就是负的
}
int main()
{
system("chcp 936");
// system("chcp 65001");
char str[] = "你好";
puts(str);
ShowBytes(str);
}
char硬编码结果
chcp 936:
浣犲ソ
E4 BD A0 E5 A5 BD
chcp 65001:
你好
E4 BD A0 E5 A5 BD
char硬编码结论
- 硬编码的char字符串与文件编码一致,此处是UTF8。
- 输出字符串时用的编码与chcp一致。
wchar_t硬编码字符串
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
void ShowBytesWide(wchar_t *str)
{
if (*str == '0')
printf("EMPTY");
while (*str != '0')
wprintf(L"%hX ", (wchar_t)*str++);
}
int main()
{
system("chcp 936");
// system("chcp 65001");
setlocale(LC_ALL, "chs");
wchar_t str[] = L"你好";
wprintf(L"%lsn", str);
// fputws(str, stdout);
ShowBytesWide(str);
}
wchar_t硬编码结果
chcp 936与chcp 65001:
你好
4F60 597D
如果注释掉setlocale,函数不会输出。
如果注释掉wprintf而改用fputws,函数不会输出,原因不明。
wchar_t硬编码结论
- 硬编码的wcahr_t字符串储存的是UTF16的编码。
- 必须使用setlocale,否则无法输出。
- wprintf输出时,会把wchar_t*转换成当前locale的编码,然后输出。
char手动输入输出
int main()
{
// system("chcp 936");
system("chcp 65001");
char str[10];
gets(str);
// scanf("%s", str);
puts(str);
ShowBytes(str);
}
char手动结果
chcp 936:
你好
你好
C4 E3 BA C3
chcp 65001:
你好
EMPTY
char手动结论
- chcp 936时能成功读入,且编码为GBK。
- chcp 65001时gets和scanf无法成功读入!
wchar_t手动输入输出
int main()
{
system("chcp 936");
// system("chcp 65001");
setlocale(LC_ALL, "chs");
wchar_t str[10];
wscanf(L"%ls", str);
wprintf(L"%lsn", str);
ShowBytesWide(str);
}
wchar_t手动结果
chcp 936:
你好
你好
4F60 597D
chcp 65001:
你好
EMPTY
wchar_t手动结论
- chcp 936时能成功读入,且编码为UTF16。
- chcp 65001时wscanf无法成功读入!
最终结论
- 说好的UTF8呢?怎么全都是GBK和UTF16?
理论上char+chcp 65001就是UTF8,然而读取失败。没有办法手动输入UTF8编码的字符串,只有编译时已经存在的能用。 - 应该如何处理中文?
chcp 936 + wchar_t。因为这是唯一成功的方案。
附加测试:wprintf的%ls
根据cppreference,%s对应的是char*
,%ls对应的才是wchar_t*
。
#include <locale.h>
#include <stdio.h>
#include <wchar.h>
int main()
{
#ifdef linux
setlocale(LC_ALL, "zh_CN.UTF-8");
#else
setlocale(LC_ALL, "chs");
#endif
char *strc = "c你好";
wchar_t *strw = L"w你好";
wprintf(L"%sn", strc);
wprintf(L"%sn", strw);
wprintf(L"%lsn", strc);
wprintf(L"%lsn", strw);
}
Windows下的测试结果:
w你好
w你好
w你好
w你好
Debian下的测试结果:
c你好
w
?�����w你好
w你好
结论:应该用printf+%s
输出char*
,用wprintf+%ls
输出wchar_t*
,混用无法正常输出。
其它说明
- 在复制chcp 65001的文本时,换行会消失。原因不明
- setlocale的第一个参数自行百度。第二个参数"chs"即对应chcp 936,为""则与当前chcp一致,为NULL则仅返回当前locale;默认为"C"
setlocale(LC_ALL, "zh-CN");
无效,原因不明。按理说微软的文档中写的是可以用的,反而文档中没有提到chs是什么意思- C语言存在一些”空终止多字节字符串“函数,但是对本文的测试没有帮助,除非你真的打算宽字符读入后再调用它们转换成UTF8来解决输入的问题
- Linux下直接用char就是UTF8,wchar_t对应的是UTF32
- WSL中的Debian无法输出宽字符,我猜其它的也不可以
- 曾经chcp 65001时会出现中文只占一个英文字符宽度的问题,但是现在1903好像没有这个问题了
- C#乱改Console.OutputEncoding和chcp有时也能正常输出中文(比如设为UTF7),这可能是因为它调用的是
WriteConsoleW
这个API。StackOverflow上有相关讨论 - 微软还提供了
TCHAR
类型:如果定义了_UNICODE
宏,就为WCHAR
(typedef wchar_t
得到),否则为char。但这些不属于C标准库,如果要用,就要再去学一下与TCHAR相关的一堆非标准库函数,我就没有研究;而且我写的代码太少,也看不出为什么要这样做