如何实现C++23风格UTF-8转UTF-32的范围遍历(基于Boost.Locale)
实现UTF-8到UTF-32的范围遍历(支持码点位置标记)
一、基于Boost.Locale的实现方案
既然你已经在使用Boost.Locale的normalize功能,直接用它内置的UTF-8迭代器就能满足需求,不需要额外引入其他组件。Boost.Locale的boost::locale::utf::utf8_iterator可以直接解析UTF-8字符串,返回对应的char32_t码点,支持完整的21位Unicode范围(0x0000~0x10FFFF)。
1. 实现管道式范围适配器
先写一个简单的适配器类,让字符串可以通过管道符|调用,适配成支持范围for的UTF-32码点序列:
#include <boost/locale/utf.hpp> #include <string> // 定义utf8to32范围适配器 struct utf8to32 { const std::string& str; explicit utf8to32(const std::string& s) : str(s) {} // 使用Boost提供的UTF-8迭代器 using iterator = boost::locale::utf::utf8_iterator<std::string::const_iterator>; iterator begin() const { return iterator(str.begin(), str.begin(), str.end()); } iterator end() const { return iterator(str.end(), str.begin(), str.end()); } }; // 重载管道运算符,支持string | utf8to32{}的写法 utf8to32 operator|(const std::string& str, utf8to32) { return utf8to32(str); }
2. 基础遍历示例
结合你已有的normalize操作,直接遍历码点:
#include <boost/locale.hpp> #include <iostream> int main() { std::string raw_utf8 = u8"Hello 世界 🌍"; // 先执行你已有的normalize操作 std::string normalized = boost::locale::normalize( raw_utf8.begin(), raw_utf8.end(), boost::locale::norm_nfc ); // 管道式遍历每个UTF-32码点 for (char32_t c : normalized | utf8to32{}) { std::cout << "码点: U+" << std::hex << static_cast<uint32_t>(c) << std::dec << std::endl; } return 0; }
3. 标记码点的起始字节位置
如果需要记录每个码点在原字符串中的起始字节索引,可以通过底层迭代器的位置计算:
size_t byte_pos = 0; auto str_begin = normalized.begin(); auto str_end = normalized.end(); while (str_begin != str_end) { // 构造UTF-8迭代器解析当前码点 boost::locale::utf::utf8_iterator<std::string::const_iterator> utf_it(str_begin, normalized.begin(), normalized.end()); char32_t c = *utf_it; // 输出码点和起始位置 std::cout << "码点U+" << std::hex << static_cast<uint32_t>(c) << std::dec << " 起始字节位置: " << byte_pos << std::endl; // 移动底层迭代器到下一个码点的起始位置 size_t bytes_used = boost::locale::utf::utf8_length(str_begin, str_end); std::advance(str_begin, bytes_used); byte_pos += bytes_used; }
二、自行实现UTF-8解析迭代器
如果无法依赖Boost.Locale,可以手动实现一个符合标准迭代器要求的UTF-8到UTF-32迭代器,支持完整21位码点解析和位置标记:
1. 自定义迭代器实现
#include <string> #include <cstdint> #include <stdexcept> #include <iterator> class utf8_to_32_iterator { public: using value_type = char32_t; using difference_type = std::ptrdiff_t; using pointer = const char32_t*; using reference = const char32_t&; using iterator_category = std::forward_iterator_tag; // 默认构造 utf8_to_32_iterator() : it_(), begin_(), end_(), current_(0), bytes_consumed_(0) {} // 带初始位置的构造 utf8_to_32_iterator(std::string::const_iterator it, std::string::const_iterator begin, std::string::const_iterator end) : it_(it), begin_(begin), end_(end), current_(0), bytes_consumed_(0) { if (it != end) parse_current(); } // 解引用 reference operator*() const { return current_; } pointer operator->() const { return ¤t_; } // 前置递增 utf8_to_32_iterator& operator++() { advance(); parse_current(); return *this; } // 后置递增 utf8_to_32_iterator operator++(int) { auto temp = *this; ++*this; return temp; } // 相等性判断 bool operator==(const utf8_to_32_iterator& other) const { return it_ == other.it_; } bool operator!=(const utf8_to_32_iterator& other) const { return !(*this == other); } // 获取当前码点的起始字节索引 size_t start_position() const { return std::distance(begin_, it_) - bytes_consumed_; } private: std::string::const_iterator it_; std::string::const_iterator begin_; std::string::const_iterator end_; char32_t current_; size_t bytes_consumed_; // 解析当前位置的UTF-8序列为码点 void parse_current() { if (it_ == end_) { current_ = 0; bytes_consumed_ = 0; return; } uint8_t first = static_cast<uint8_t>(*it_); if ((first & 0x80) == 0) { // 1字节ASCII码点 current_ = first; bytes_consumed_ = 1; } else if ((first & 0xE0) == 0xC0) { // 2字节序列 check_continuation(1); uint8_t second = static_cast<uint8_t>(*std::next(it_)); current_ = static_cast<char32_t>((first & 0x1F) << 6) | (second & 0x3F); bytes_consumed_ = 2; } else if ((first & 0xF0) == 0xE0) { // 3字节序列 check_continuation(2); uint8_t second = static_cast<uint8_t>(*std::next(it_)); uint8_t third = static_cast<uint8_t>(*std::next(it_, 2)); current_ = static_cast<char32_t>((first & 0x0F) << 12) | ((second & 0x3F) << 6) | (third & 0x3F); bytes_consumed_ = 3; } else if ((first & 0xF8) == 0xF0) { // 4字节序列(支持21位码点) check_continuation(3); uint8_t second = static_cast<uint8_t>(*std::next(it_)); uint8_t third = static_cast<uint8_t>(*std::next(it_, 2)); uint8_t fourth = static_cast<uint8_t>(*std::next(it_, 3)); current_ = static_cast<char32_t>((first & 0x07) << 18) | ((second & 0x3F) << 12) | ((third & 0x3F) << 6) | (fourth & 0x3F); bytes_consumed_ = 4; // 检查码点是否在Unicode合法范围内 if (current_ > 0x10FFFF) { throw std::invalid_argument("UTF-8序列超出Unicode范围"); } } else { throw std::invalid_argument("无效的UTF-8起始字节"); } } // 检查后续字节是否足够且合法 void check_continuation(size_t count) { if (std::next(it_, count) > end_) { throw std::invalid_argument("截断的UTF-8序列"); } for (size_t i = 1; i <= count; ++i) { uint8_t byte = static_cast<uint8_t>(*std::next(it_, i)); if ((byte & 0xC0) != 0x80) { throw std::invalid_argument("无效的UTF-8续字节"); } } } // 移动迭代器到下一个码点的起始位置 void advance() { std::advance(it_, bytes_consumed_); } }; // 范围适配器 struct utf8to32 { const std::string& str; explicit utf8to32(const std::string& s) : str(s) {} using iterator = utf8_to_32_iterator; iterator begin() const { return iterator(str.begin(), str.begin(), str.end()); } iterator end() const { return iterator(str.end(), str.begin(), str.end()); } }; // 管道运算符重载 utf8to32 operator|(const std::string& str, utf8to32) { return utf8to32(str); }
2. 使用自定义迭代器的示例
#include <iostream> int main() { std::string utf8_str = u8"Hello 世界 🌍"; try { auto range = utf8_str | utf8to32{}; for (auto it = range.begin(); it != range.end(); ++it) { std::cout << "码点U+" << std::hex << static_cast<uint32_t>(*it) << std::dec << " 起始字节位置: " << it->start_position() << std::endl; } } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << std::endl; } return 0; }
内容的提问来源于stack exchange,提问作者user11566470
相关产品推荐
相关产品推荐

