Windows下c++字符编码(二)

编译器对c++源文件编码的识别

注意,这里说的,是对于源文件编码的识别,而不是用c++来读写文件。实际上,不同编译器对于源文件编码的识别时有差异的,这是你需要特别注意的一点。如果你在 Code Blocks 里边在源文件中写了中文,用 sublime 打开却发现出现乱码,本文可以彻底解决这个问题。

基本知识和基本工具

本篇是整理时加上的一节,我觉得最好将一些基础知识提前说清楚,便于之后的陈述。

文件编码基础知识

前面说完了基本的编码知识,具体在文件中是这么实现的呢。

  • 文本文件

通常意义上理解的文本文件,就是只保存字符的编码信息,而不保存字符的字体、大小、颜色等信息的文件。事实上,所有的编程源文件都是文本文件。

在windows下我们新建一个文本文件,一般默认是使用Notepad这个软件,默认编码是ANSI。不过如果你采用其他软件,例如Notepad++,vim,或者VS Code,结果可能是不尽相同。但是无论如何,字符在文件中保存,一定还是0101这样的编码(计算机上什么东西不是这样编码的呢)。

  • ANSI

这个基本上没有什么好说的,windows下使用记事本(Notepad)新建一个文本文件,就是ANSI编码的。其在硬盘上保存的,就是这些字符的ANSI编码。

  • utf-8

现在有一个问题,同样一个.txt文件,编辑器如何判断它是什么编码呢。所以很多文件都有一些打头的一些字节来表示自己的文件类型。一些utf-8文件就会有文件头,称为BOM,utf-8文件的BOM是三个字节:0xef0xbb0xbf。不过一些由于utf-8编码的普及,现在无BOM的utf-8文件更多。

作为utf-8拥护者,我们一般推崇无BOM的utf-8文件

比如 sublime 识别文件默认就是utf-8,如果文件编码是utf-8,就会正确显示,如果文件实际上是ANSI编码,显然不会有BOM,sublime 还是把他当成utf-8文件来读,就会产生乱码。

  • utf-16文件

前面说过,utf-16实际上有两种存储方式,大端序和小端序,这关系到一个utf-16字符两个字节哪个在硬盘中位置靠前。这个信息要告诉编辑器,就依靠文件头了:

​ - 大端序utf-16BE,文件头为0xfe,0xff。

​ - 小端序utf-16LE,文件头文0xff,0xfe。

  • utf-32文件

目前我的工具无法查看utf-32文件,就不管了,一般来说utf-32文件不会用到。

工具
  • Notepad++

我的主要工具就是Notepad++了,它会判断文件编码,可以很方便地转换编码,虽然我现在常用VS Code,不过写此文时,Notepad++帮了大忙,如果你碰到乱码文本文件,可以用他来查看。

  • cmd(命令提示符)

要特别注意的是,命令提示符的输入输出默认编码是ANSI,所以任何输出字节都会按照ANSI解码,然后再打印出来。所以,通常只有ANSI编码字符串才能正确输出,如何输出其他编码字符,是后面的课题。

python3的str的默认编码是utf-8,其可以正确输出是python自己实现的。

c++字符串的基本知识
  • 字符初始化
类型名单元初始化(注意前缀)编码
std::stringcharstd::string s = "你好世界";系统默认
std::stringcharstd::string s = u8"你好世界";utf-8
std::u16stringchar16_tstd::u16string s = u"你好世界";utf-16
std::u32stringchar32_tstd::u32string s = U"你好世界";utf-32
std::wstringwchar_tstd::wstring s = L"你好世界";系统依赖
  • wchar_t的长度
sizeof(wchar_t) == 2; //Winodws下
sizeof(wchar_t) == 4; //Unix/Linux下

编译器对源文件的识别

实验使用的编译器有visual studio 2017的cl.exe(x86_x64),clang 7.0.0(自己编译的release版本),以及gcc 8.1.0 (MinGW-W64),全部都在命令行下编译,实验时会具体说明。所有实验源文件编码也会标注。

#include <iostream>
#include <string>
// encoding = utf-8 (no BOM)

int main(){
    std::string sa = "你好世界";
    std::string sb = u8"你好世界";
    std::cout << "len(sa)=" << sa.size() << ' ' << sa << std::endl;
    std::cout << "len(sb)=" << sb.size() << ' ' << sb << std::endl;
}

输出:

  • g++ or clang-cl
len(sa)=12 浣犲ソ涓栫晫
len(sb)=12 浣犲ソ涓栫晫
  • cl.exe
len(sa)=12 浣犲ソ涓栫晫
len(sb)=18 娴g姴銈芥稉鏍櫕

如果上述源文件改成ANSI编码,结果是:

  • g++
len(sa)=8 你好世界
len(sb)=8 你好世界
  • clang-cl
test.cpp(6,23):  warning: illegal character encoding in string literal [-Winvalid-source-encoding]
    std::string sa = "<C4><E3><BA><C3><CA><C0><BD><E7>";
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp(7,25):  error: illegal character encoding in string literal
    std::string sb = u8"<C4><E3><BA><C3><CA><C0><BD><E7>";
  • cl.exe
len(sa)=8 你好世界
len(sb)=12 浣犲ソ涓栫晫

分析:从上诉结果可以看出,

  • gcc和clang都是默认utf-8编码的,不过,g++没有编码检查,会把任何文件都当作utf-8文件来读取。
  • gcc和clang的u8前缀是不起作用的,毕竟本来就默认是utf-8编码。
  • cl 默认编码是ANSI,所以u8前缀是起作用的,但是对于无BOM的utf-8文件,它会当成ANSI来读取,所以它把utf-8的"你好世界"含有的12字节当成了6个ANSI字符,并对其进行的转码,试图转为6个utf-8字符,也就是18字节,这显然是越搞越乱。

不过还是可以夸夸cl的,如果你用其他编码实验,对于cl,它都会给出:

len(sa)=8 你好世界
len(sb)=12 浣犲ソ涓栫晫

这是由于它会自动把他转换为ANSI编码,然后再编译,包括有BOM的utf-8文件也是这样的(显然,有BOM就是告诉了它这是一个utf-8文件,无BOM它就当是ANSI了)。

而对于gcc和clang,其中ANSI和utf-8都会按照utf-8文件来读取,但是对于clang,string中的非utf-8字符会报错。而其他编码,gcc和calng都不支持,不过,gcc会报出一系列很难看懂的错误,但是clang会贴心地告诉你:

fatal error: UTF-16 (LE) byte order mark detected in 'test.cpp', but encoding is not supported
1 error generated.

总结如下

  • cl.exe默认编码是ANSI,它会将其他文件先转化成ANSI编码,再编译。但是对于无BOM的utf-8文件,会当成ANSI从而可能出错。
  • gcc和clang都默认utf-8,clang拒绝ANSI的string(不过注释可以),gcc会把ANSI也当成utf-8,其他编码源文件都不支持。
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值