C++ UTF-8 编码与 UTF-32 编码的互相转换

C++ UTF-8 编码与 UTF-32 编码的互相转换

代码实现基本照搬了秦建辉的博客。

这里不介绍原理,只提供可以直接使用的代码。要求 C++ 编译器的语言标准至少为 C++17。如果编译器支持的语言标准达 C++20,则可定义宏 __stdge20,以使用 C++20 的新特性,如 char8_t、概念等。

以下代码没有考虑性能。

// utf_conv.hpp
// coded by UnnamedOrange.
// include to use.

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 

// #define __stdge20 1 // 语言标准支持 C++20。

#if !__stdge20
using char8_t = char;
namespace std
{
	using u8string = std::basic_string;
	using u8string_view = std::basic_string_view;
}
#endif

namespace utfconv
{
	/// 
	/// 在 UTF 编码间进行转换。只有部分特化模板具有实现。
	/// 
	/// 源的字符类型。
	/// 目标字符类型。如果是宽字符,使用小端编码。
	template 
	class utf_conv {};
	/// 
	/// 在 UTF 编码间进行转换时出错。
	/// 
	class utf_conv_error : public std::runtime_error { using std::runtime_error::runtime_error; };

	/// 
	/// 从 UTF-8 转换到 UTF-32(LE)。
	/// 
	/// char 或者 char8_t(C++20)。
	template 
	class utf_conv
	{
	private:
		static std::tuple convert_once(std::basic_string_view src)
		{
			if (src.empty())
				return { 0, 0 };

			char32_t ret{};
			size_t len{};
			std::make_unsigned_t b = src.front();

			if (b < 0x80) // 单字符。
				return { b, 1 };
			else if (b < 0xC0 || b > 0xFD) // 非法值。
				throw utf_conv_error("fail to convert_once. invalid utf-8 char.");
			else if (b < 0xE0)
			{
				ret = b & 0x1F;
				len = 2;
			}
			else if (b < 0xF0)
			{
				ret = b & 0x0F;
				len = 3;
			}
			else if (b < 0xF8u)
			{
				ret = b & 7;
				len = 4;
			}
			else if (b < 0xFCu)
			{
				ret = b & 3;
				len = 5;
			}
			else
			{
				ret = b & 1;
				len = 6;
			}

			for (size_t i = 1; i < len; i++)
			{
				b = src[i];
				if (b < 0x80 || b > 0xBF) // 非法值。
					break;

				ret = (ret << 6) + (b & 0x3F);
			}
			return { ret, len };
		}
	public:
		[[nodiscard]] static std::u32string convert(std::basic_string_view src)
#if __stdge20
			requires std::is_same_v || std::is_same_v
#endif
		{
#if !__stdge20
			static_assert(std::is_same_v, "src_t and des_t is invalid.");
#endif
			size_t length{};
			decltype(convert_once(src)) t;
			for (size_t i = 0; i < src.length(); i += std::get<1>(t))
			{
				t = convert_once(src.substr(i));
				length++;
			}
			std::vector ret(length + 1);
			length = 0;
			for (size_t i = 0; i < src.length(); i += std::get<1>(t))
			{
				t = convert_once(src.substr(i));
				ret[length++] = std::get<0>(t);
			}
			return ret.data();
		}
	};
	/// 
	/// 从 UTF-32(LE) 转换到 UTF-8。
	/// 
	/// char 或者 char8_t(C++20)。
	template 
	class utf_conv
	{
	private:
		static std::tuple, size_t> convert_once(char32_t ch)
		{
			constexpr std::array, 6> prefix
			{ 0, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
			constexpr std::array code_up
			{ 0x80, 0x800, 0x10000, 0x200000, 0x4000000, 0x80000000 };

			std::array ret{};
			size_t len{};
			// 根据 UTF-32 编码范围确定对应的 UTF-8 编码字节数。
			bool valid{};
			for (size_t i = 0; i < code_up.size(); i++)
				if (ch < code_up[i])
				{
					len = i + 1;
					valid = true;
					break;
				}
			if (!valid)
				throw utf_conv_error("fail to convert_once. invalid utf-32 char.");

			for (size_t i = len - 1; i; i--)
			{
				ret[i] = static_cast((ch & 0x3F) | 0x80);
				ch >>= 6;
			}
			ret[0] = static_cast(ch | prefix[len - 1]);
			return { ret, len };
		}
	public:
		[[nodiscard]] static std::basic_string convert(std::u32string_view src)
#if __stdge20
			requires std::is_same_v || std::is_same_v
#endif
		{
#if !__stdge20
			static_assert(std::is_same_v, "src_t and des_t is invalid.");
#endif
			size_t length{};
			decltype(convert_once(src[0])) t;
			for (size_t i = 0; i < src.length(); i++)
			{
				t = convert_once(src[i]);
				length += std::get<1>(t);
			}
			std::vector ret(length + 1);
			length = 0;
			for (size_t i = 0; i < src.length(); i++)
			{
				t = convert_once(src[i]);
				for (size_t j = 0; j < std::get<1>(t); j++)
					ret[length++] = std::get<0>(t)[j];
			}
			return ret.data();
		}
	};

	[[nodiscard]] inline std::u8string operator"" _u8(const char32_t* _Str, size_t _Len) {
		return utf_conv::convert(std::u32string_view(_Str, _Len));
	}
}

使用示例:

std::u8string u8result = utfconv::utfconv::convert(U"好支持顶");
using namespace utfconv;
std::u8string u8literal_nonconstexpr = U"Windows 下在 C++17 标准中构建 UTF-8 字面量的折中办法。"_u8;
std::u32string u32result = utfconv::utfconv::convert("Windows 下不能这么写,因为 Windows 的 char 类型对应的字符串默认是 ANSI 字符集而非 UTF-8。");

你可能感兴趣的:(C++)