conststd::wstring如何编码以及如何更改为UTF-16
我创建了这个最小的工作 C++ 示例代码片段来比较 astd::string和 a中的字节(通过它们的十六进制表示)在std::wstring定义一个带有德国非 ASCII 字符的字符串时。
#include <iostream>
#include <iomanip>
#include <string>
int main(int, char**) {
std::wstring wstr = L"äöüß";
std::string str = "äöüß";
for ( unsigned char c : str ) {
std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast<unsigned short>(c) << ' ';
}
std::cout << std::endl;
for ( wchar_t c : wstr ) {
std::cout << std::setw(4) << std::setfill('0') << std::hex << static_cast<unsigned short>(c) << ' ';
}
std::cout << std::endl;
return 0;
}
这个片段的输出是
c3 a4 c3 b6 c3 bc c3 9f
00c3 00a4 00c3 00b6 00c3 00bc 00c3 0178
我在自己运行Windows 10 64-bit Pro的 PC 上运行它,使用版本 16.8.1 中的MSVC 2019 社区版编译,使用构建系统cmake和以下内容CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0)
project(wstring VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
include(CTest)
enable_testing()
add_executable(wstring main.cpp)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
我读到,这std::string是基于char单字节的类型。我看到我的代码片段的输出表明str(std::string变量)是UTF-8编码的。我读到,Microsoft 编译器使用wchar_ts 和 2 个字节来组成std::wstrings(而不是 4 个字节wchar_ts,例如 GNU gcc),因此希望wstr(std::wstring变量)是(任何类型的)UTF-16编码。但我无法弄清楚为什么“ß”(拉丁文尖 s)按照0x00c30178我的预期进行编码0x00df。请有人告诉我:
- 为什么会发生这种情况?
- 我怎么会得到 UTF-16 编码的
std::wstrings(Big Endian 没问题,我不介意 BOM)?我可能需要以某种方式告诉编译器吗? - 这是一种什么样的编码?
编辑 1
更改了标题,因为它不适合问题(实际上 UTF-8 和 UTF-16 是不同的编码,所以我自己已经找到了新的答案......)
编辑 2
忘了提:我使用amd64提到的编译器的目标
编辑 3
如果添加/utf-8dxiv 在评论中指出的标志(请参阅他链接的 SO-Post),我会得到所需的输出
c3 a4 c3 b6 c3 bc c3 9f
00e4 00f6 00fc 00df
对我来说看起来像 UTF-16-BE(没有 BOM)。由于我对 cmake 命令的正确顺序有疑问,这是我当前的CmakeLists.txt文件。将add_compile_options命令放在命令之前很重要add_executable(为了方便,我添加了通知)
cmake_minimum_required(VERSION 3.0.0)
project(enctest VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
include(CTest)
enable_testing()
if (MSVC)
message(NOTICE "compiling with MSVC")
add_compile_options(/utf-8)
endif()
add_executable(enctest main.cpp)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
我发现这种if-endif方式比生成器语法更具可读性,但写作add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")也可以。
注意:对于 Qt-Projects,.pro文件有一个很好的开关(请参阅此 Qt-Form 帖子)
win32 {
QMAKE_CXXFLAGS += /utf-8
}
我的问题的第一部分仍然是开放的:0x00c30178“ß”(拉丁文尖 s)的编码是什么?
回答
如注释中所述,源.cpp文件采用 UTF-8 编码。如果没有 BOM 和显式/source-charset:utf-8开关,Visual C++ 编译器默认假设源文件以活动代码页编码保存。从设置源字符集文档:
默认情况下,Visual Studio 检测字节顺序标记以确定源文件是否采用编码的 Unicode 格式,例如 UTF-16 或 UTF-8。如果未找到字节顺序标记,则假定源文件使用当前用户代码页进行编码,除非您使用 /source-charset 选项指定字符集名称或代码页。
的 UTF-8 编码äöüß是C3 A4 C3 B6 C3 BC C3 9F,因此该行:
std::wstring wstr = L"äöüß";
被编译器视为:
std::wstring wstr = L"xC3xA4xC3xB6xC3xBCxC3x9F"`;
假设活动代码页是通常的Windows-1252,(扩展)字符映射为:
win-1252 char unicode
xC3 Ã U+00C3
xA4 ¤ U+00A4
xB6 ¶ U+00B6
xBC ¼ U+00BC
x9F Ÿ U+0178
因此L"xC3xA4xC3xB6xC3xBCxC3x9F"被翻译为:
std::wstring wstr = L"u00C3u00A4u00C3u00B6u00C3u00BCu00C3u0178"`;
为了避免这种(错误)翻译,需要通过显式/source-charset:utf-8(或/utf-8)编译器开关告诉 Visual C++ 源文件编码为 UTF-8 。对于基于 CMake 的项目,这可以使用add_compile_options如可能强制 CMake/MSVC 对没有 BOM 的源文件使用 UTF-8 编码中所示来完成?C4819。