如何在 C++ 中获得 std::u8string 的正确长度?
How to get correct length of std::u8string in C++?
如何获得 std::u8string 的正确长度? (在 C++20 中)
我已尝试使用以下代码打印不正确的长度值,该值可能 returns 代码点数的值。
如何获得我期望的 7 个字符的正确值?
int main() {
const char8_t* s = u8"Hello";
auto st = std::u8string(s);
std::cout << st.size() << std::endl;
}
就大多数 C++ 函数而言,A u8string
实际上是一个字节序列。因此 size()
给你 13 (48 65 6c 6c 6f f0 9f 98 83 f0 9f 98 83
)。 "" ("SMILING FACE WITH OPEN MOUTH" U+1F603) 被编码为 4 个元素 f0 9f 98 83
。您也会在 [i]
、substr
等中看到这一点。
知道是UTF-8,可以统计一下Unicode码位的个数。您可以使用 u32string
这是代码点。我不相信 C++ 有直接在开箱即用的 u8string
上这样做的功能:
size_t count_codepoints(const std::u8string &str)
{
size_t count = 0;
for (auto &c : str)
if ((c & 0b1100'0000) != 0b1000'0000) // Not a trailing byte
++count;
return count;
}
然而,这可能仍然不是人们认为的 "number of character"。这是因为多个代码点可能用于表示单个可见字符 "combining characters"。其中一些也有 "precomposed" 形式,组合代码点的顺序可能不同,导致 "normal forms" 和比较 Unicode 字符串的问题。例如“Á”可能是 "LATIN CAPITAL LETTER A WITH ACUTE' (U+00C1)",它是 UTF-8 C3 81
,或者它可能有一个正常的 "A" 和一个 "COMBINING ACUTE ACCENT (U+0301)",它是两个代码点和 3 个 UTF- 8 个字节 41 CC 81
.
unicode.org 中的每个 Unicode 版本都有表格,可让您正确处理和转换组合字符(以及 upper/lower 大小写转换之类的东西),但它们非常广泛,您需要编写一些代码来处理它们。第 3 方库(我认为 Linux 主要使用 ICU)或 OS 函数(Window 有一堆 API)也提供各种实用程序。
值得注意的是,您可以 运行 在许多其他 cases/languages 中解决这些问题,而不仅仅是 C++。例如Java脚本、Java 和 .NET,以及 Windows C/C++ API(本质上是 Windows 上的 wchar_t
)使用具有 "surrogate pairs" 的 UTF-16 字符串用于一些代码点,许多函数实际上计算 UTF-16 元素,而不是代码点。
标准的 C++ 答案是将字符串从 utf8 转换为 utf32,然后检查大小。
令人担忧的是,从 c++17 开始,std::wstring_convert
现已弃用。我不知道替代品是什么。
#include <string>
#include <iostream>
#include <cstdlib>
#include <locale>
#include <codecvt>
auto convert(std::u8string input) -> std::u32string
{
auto first = reinterpret_cast<const char*>(input.data());
auto last = first + input.size();
auto result = std::u32string();
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> ucs4conv;
try
{
result = ucs4conv.from_bytes(first, last);
}
catch(const std::range_error& e) {
last = first + ucs4conv.converted();
std::clog << "UCS4 failed after consuming " << std::dec << std::distance(first, last) <<" characters:\n";
result = ucs4conv.from_bytes(first, last);
}
return result;
}
int main() {
const char8_t* s = u8"Hello";
auto st = std::u8string(s);
std::cout << "bytes : " << st.size() << std::endl;
auto ws = convert(st);
std::cout << "wide chars : " << ws.size() << std::endl;
}
预期输出:
bytes : 13
wide chars : 7
其他答案已经建议了计算代码点数量的方法,如果这确实是您的用例所需要的。我添加这个答案是为了说明代码点长度可能不是您想要的。
实际上,我不会亲自说明这一点。相反,我将向一个优秀的博客 post 提供一个 link 来解释这些问题,以便您可以评估您实际需要的信息。
如何获得 std::u8string 的正确长度? (在 C++20 中) 我已尝试使用以下代码打印不正确的长度值,该值可能 returns 代码点数的值。
如何获得我期望的 7 个字符的正确值?
int main() {
const char8_t* s = u8"Hello";
auto st = std::u8string(s);
std::cout << st.size() << std::endl;
}
A u8string
实际上是一个字节序列。因此 size()
给你 13 (48 65 6c 6c 6f f0 9f 98 83 f0 9f 98 83
)。 "" ("SMILING FACE WITH OPEN MOUTH" U+1F603) 被编码为 4 个元素 f0 9f 98 83
。您也会在 [i]
、substr
等中看到这一点。
知道是UTF-8,可以统计一下Unicode码位的个数。您可以使用 u32string
这是代码点。我不相信 C++ 有直接在开箱即用的 u8string
上这样做的功能:
size_t count_codepoints(const std::u8string &str)
{
size_t count = 0;
for (auto &c : str)
if ((c & 0b1100'0000) != 0b1000'0000) // Not a trailing byte
++count;
return count;
}
然而,这可能仍然不是人们认为的 "number of character"。这是因为多个代码点可能用于表示单个可见字符 "combining characters"。其中一些也有 "precomposed" 形式,组合代码点的顺序可能不同,导致 "normal forms" 和比较 Unicode 字符串的问题。例如“Á”可能是 "LATIN CAPITAL LETTER A WITH ACUTE' (U+00C1)",它是 UTF-8 C3 81
,或者它可能有一个正常的 "A" 和一个 "COMBINING ACUTE ACCENT (U+0301)",它是两个代码点和 3 个 UTF- 8 个字节 41 CC 81
.
unicode.org 中的每个 Unicode 版本都有表格,可让您正确处理和转换组合字符(以及 upper/lower 大小写转换之类的东西),但它们非常广泛,您需要编写一些代码来处理它们。第 3 方库(我认为 Linux 主要使用 ICU)或 OS 函数(Window 有一堆 API)也提供各种实用程序。
值得注意的是,您可以 运行 在许多其他 cases/languages 中解决这些问题,而不仅仅是 C++。例如Java脚本、Java 和 .NET,以及 Windows C/C++ API(本质上是 Windows 上的 wchar_t
)使用具有 "surrogate pairs" 的 UTF-16 字符串用于一些代码点,许多函数实际上计算 UTF-16 元素,而不是代码点。
标准的 C++ 答案是将字符串从 utf8 转换为 utf32,然后检查大小。
令人担忧的是,从 c++17 开始,std::wstring_convert
现已弃用。我不知道替代品是什么。
#include <string>
#include <iostream>
#include <cstdlib>
#include <locale>
#include <codecvt>
auto convert(std::u8string input) -> std::u32string
{
auto first = reinterpret_cast<const char*>(input.data());
auto last = first + input.size();
auto result = std::u32string();
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> ucs4conv;
try
{
result = ucs4conv.from_bytes(first, last);
}
catch(const std::range_error& e) {
last = first + ucs4conv.converted();
std::clog << "UCS4 failed after consuming " << std::dec << std::distance(first, last) <<" characters:\n";
result = ucs4conv.from_bytes(first, last);
}
return result;
}
int main() {
const char8_t* s = u8"Hello";
auto st = std::u8string(s);
std::cout << "bytes : " << st.size() << std::endl;
auto ws = convert(st);
std::cout << "wide chars : " << ws.size() << std::endl;
}
预期输出:
bytes : 13
wide chars : 7
其他答案已经建议了计算代码点数量的方法,如果这确实是您的用例所需要的。我添加这个答案是为了说明代码点长度可能不是您想要的。
实际上,我不会亲自说明这一点。相反,我将向一个优秀的博客 post 提供一个 link 来解释这些问题,以便您可以评估您实际需要的信息。