conststd::wstring是怎么编码的,怎么改成UTF-16
How is const std::wstring encoded and how to change to UTF-16
我创建了这个最小工作 C++ 示例片段来比较 std::string
和 std::wstring
中的字节(通过它们的十六进制表示),当定义一个带有德语 non-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
我 运行 在 PC 上 运行 本身 Windows 10 64 位 Pro,用 MSVC 编译2019 Community Edition 版本 16.8.1,使用构建系统 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_t
s 和 2 个字节来组成 std::wstring
s(而不是 4 字节 wchar_t
s,例如 GNU gcc)因此会期望 wstr
(std::wstring
变量)进行(任何类型的)UTF-16 编码。但我无法弄清楚为什么“ß”(拉丁尖锐 s)被编码为 0x00c30178
而不是我所期望的 0x00df
。有人可以告诉我吗:
- 为什么会这样?
- 我怎样才能得到 UTF-16 编码的
std::wstring
s(Big Endian 就可以了,我不介意 BOM)?我可能需要以某种方式告诉编译器吗?
- 这是什么编码?
编辑 1
更改了标题,因为它不适合问题(实际上 UTF-8 和 UTF-16 是不同的编码,所以我自己已经有了新的答案...)
编辑 2
忘记提及:我使用上述编译器的amd64
目标
编辑 3
如果添加 /utf-8
标志,如 dxiv 评论中指出的那样(参见 ),我会得到所需的输出
c3 a4 c3 b6 c3 bc c3 9f
00e4 00f6 00fc 00df
这对我来说看起来像 UTF-16-BE(无 BOM)。由于我对 cmake 命令的正确顺序有疑问,这是我当前的 CmakeLists.txt
文件。重要的是把add_compile_options
命令放在add_executable
命令之前(为了方便我加了Notice)
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
方式比 generator-syntax 方式更具可读性,但写成 add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
也可以。
注意:对于Qt-Projects,.pro
文件有一个很好的开关(参见this Qt-Form post)
win32 {
QMAKE_CXXFLAGS += /utf-8
}
我的问题的第一部分仍然悬而未决:0x00c30178
“ß”(拉丁升号 s)的编码是什么?
therefore would expect wstr (the std::wstring variable) to be (any kind of) UTF-16 encoded
std::wstring
未指定编码。它是一系列“宽字符”,用于某种宽字符(由实现定义)。
standard library 中定义了转换方面,用于转换 to/from 不同的编码。
如评论中所述,源 .cpp
文件采用 UTF-8 编码。没有 BOM,也没有明确的 /source-charset:utf-8
开关,Visual C++ 编译器默认假定源文件保存在活动代码页编码中。来自 Set Source Character Set 文档:
By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page, unless you specify a character set name or code page by using the /source-charset option.
äöüß
的 UTF-8 编码是 C3 A4 C3 B6 C3 BC C3 9F
,因此行:
std::wstring wstr = L"äöüß";
被编译器视为:
std::wstring wstr = L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"`;
假设活动代码页是通常的 Windows-1252,(扩展)字符映射为:
win-1252 char unicode
\xC3 Ã U+00C3
\xA4 ¤ U+00A4
\xB6 ¶ U+00B6
\xBC ¼ U+00BC
\x9F Ÿ U+0178
因此 L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"
被翻译成:
std::wstring wstr = L"\u00C3\u00A4\u00C3\u00B6\u00C3\u00BC\u00C3\u0178"`;
为了避免这种(错误的)翻译,需要通过显式 /source-charset:utf-8
(或 /utf-8
) compiler switch. For CMake based projects, this can be done using add_compile_options
as shown at .
我创建了这个最小工作 C++ 示例片段来比较 std::string
和 std::wstring
中的字节(通过它们的十六进制表示),当定义一个带有德语 non-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
我 运行 在 PC 上 运行 本身 Windows 10 64 位 Pro,用 MSVC 编译2019 Community Edition 版本 16.8.1,使用构建系统 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_t
s 和 2 个字节来组成 std::wstring
s(而不是 4 字节 wchar_t
s,例如 GNU gcc)因此会期望 wstr
(std::wstring
变量)进行(任何类型的)UTF-16 编码。但我无法弄清楚为什么“ß”(拉丁尖锐 s)被编码为 0x00c30178
而不是我所期望的 0x00df
。有人可以告诉我吗:
- 为什么会这样?
- 我怎样才能得到 UTF-16 编码的
std::wstring
s(Big Endian 就可以了,我不介意 BOM)?我可能需要以某种方式告诉编译器吗? - 这是什么编码?
编辑 1
更改了标题,因为它不适合问题(实际上 UTF-8 和 UTF-16 是不同的编码,所以我自己已经有了新的答案...)
编辑 2
忘记提及:我使用上述编译器的amd64
目标
编辑 3
如果添加 /utf-8
标志,如 dxiv 评论中指出的那样(参见
c3 a4 c3 b6 c3 bc c3 9f
00e4 00f6 00fc 00df
这对我来说看起来像 UTF-16-BE(无 BOM)。由于我对 cmake 命令的正确顺序有疑问,这是我当前的 CmakeLists.txt
文件。重要的是把add_compile_options
命令放在add_executable
命令之前(为了方便我加了Notice)
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
方式比 generator-syntax 方式更具可读性,但写成 add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
也可以。
注意:对于Qt-Projects,.pro
文件有一个很好的开关(参见this Qt-Form post)
win32 {
QMAKE_CXXFLAGS += /utf-8
}
我的问题的第一部分仍然悬而未决:0x00c30178
“ß”(拉丁升号 s)的编码是什么?
therefore would expect wstr (the std::wstring variable) to be (any kind of) UTF-16 encoded
std::wstring
未指定编码。它是一系列“宽字符”,用于某种宽字符(由实现定义)。
standard library 中定义了转换方面,用于转换 to/from 不同的编码。
如评论中所述,源 .cpp
文件采用 UTF-8 编码。没有 BOM,也没有明确的 /source-charset:utf-8
开关,Visual C++ 编译器默认假定源文件保存在活动代码页编码中。来自 Set Source Character Set 文档:
By default, Visual Studio detects a byte-order mark to determine if the source file is in an encoded Unicode format, for example, UTF-16 or UTF-8. If no byte-order mark is found, it assumes the source file is encoded using the current user code page, unless you specify a character set name or code page by using the /source-charset option.
äöüß
的 UTF-8 编码是 C3 A4 C3 B6 C3 BC C3 9F
,因此行:
std::wstring wstr = L"äöüß";
被编译器视为:
std::wstring wstr = L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"`;
假设活动代码页是通常的 Windows-1252,(扩展)字符映射为:
win-1252 char unicode
\xC3 Ã U+00C3
\xA4 ¤ U+00A4
\xB6 ¶ U+00B6
\xBC ¼ U+00BC
\x9F Ÿ U+0178
因此 L"\xC3\xA4\xC3\xB6\xC3\xBC\xC3\x9F"
被翻译成:
std::wstring wstr = L"\u00C3\u00A4\u00C3\u00B6\u00C3\u00BC\u00C3\u0178"`;
为了避免这种(错误的)翻译,需要通过显式 /source-charset:utf-8
(或 /utf-8
) compiler switch. For CMake based projects, this can be done using add_compile_options
as shown at