【字符编码转换】使用iconv

一、配置

        配置iconv在windows平台会比较麻烦,下面这个是使用vs2022编译的,可以直接使用,其他vs版本自求多福。

https://github.com/pffang/libiconv-for-Windows

        引入头文件<iconv.h>和lib文件即可。

        官网:libiconv - GNU Project - Free Software Foundation (FSF)

二、使用

        使用倒比较简单,只有三个函数:

iconv_open设置要转换的编码和转为的编码
iconv转换操作
iconv_close资源释放

1.iconv_open

iconv_t cd = iconv_open(to, from);
if ((iconv_t)(-1) == cd)
{
	assert(0 && "iconv_open失败!");// 会递归调用自己,不能使用debug
	return {};
}

        函数返回一个句柄iconv_t,如果返回-1代表失败。出现错误可能是因为to、from设置的编码不支持,但是这里不能使用debug输出日志,因为我在debug函数将utf8编码转为本地编码使用了此函数,就发生了递归调用从而栈溢出。读者可以自行考虑怎么处理错误。

2.iconv_close

//资源释放
finally f([&]() {
	iconv_close(cd);
});

//.h
/**
  * @brief RAII清理操作
*/
template<typename F>
class finally
{
public:
	finally(F&& func) : _func(func) {}
	~finally() { _func(); }
private:
	F _func;
};

        上面这种写法无论函数在哪里return,只要脱离f的作用域,iconv句柄就会被调用iconv_close从而释放。

3.iconv

char* in_buf = (char*)buf;	//输入指针
size_t in_size = buf_size;	//输入大小,直到0停止转换
char* out_buf = temp;		//输出缓冲区指针
size_t out_size = SIZE_BUFFER_STRING_CONVERT;//输出缓冲区大小

if (-1 == iconv(cd, &in_buf, &in_size, &out_buf, &out_size))
{
}

        这里需要句柄cd加上4个变量给予iconv函数,后面4个参数均是取地址,iconv函数会改变它们的值。

        in_size我们设为要转换字符串(输入缓冲区in_buf)的大小(字节单位),当完全转换时,in_size就会变为0。那么初始大小buf_size减去in_size就是当前已经转换了的字符数量。

        同理输出缓冲区大小SIZE_BUFFER_STRING_CONVERT减去out_size,就是单次调用iconv转换产生的输出大小(字节单位)。

char temp[SIZE_BUFFER_STRING_CONVERT];//缓冲区
string ret;//输出

        之所以设置一个固定的输出缓冲区大小,是因为当要转换的字符串过大时,就很难一次转换完成,只能先转换一部分复制到最终的输出(string ret),然后再转换再添加到输出字符串。

        所以实际代码是一个循环,返回-1并不是真的失败,当errno为E2BIG时是输出缓冲区不够用了:

do
{//直到转换完
	if (-1 == iconv(cd, &in_buf, &in_size, &out_buf, &out_size))
	{
		if (errno != E2BIG)
		{
			assert(0 && "转换编码失败!");
			return {};
		}
		else
		{
			size_t num = SIZE_BUFFER_STRING_CONVERT - out_size;
			ret += string(temp, num);
			//没有转换完,重设输出位置
			out_size = SIZE_BUFFER_STRING_CONVERT;
			out_buf = temp;
		}
	}
	else
	{//完整转换,增加num
		assert(in_size == 0);
		size_t num = SIZE_BUFFER_STRING_CONVERT - out_size;
		if (ret.empty())//第一次就结束了,优化直接return
			return string(temp, num);
		ret += string(temp, num);
	}
} while (in_size);

三、完整代码

        整个关键的封装函数即是CvtString,代码如下:

string String::CvtString(const char* from, const char* to, const char* buf, size_t buf_size)
{
	//前置条件检查
	if (buf_size == 0)
		return {};

	iconv_t cd = iconv_open(to, from);
	if ((iconv_t)(-1) == cd)
	{
		assert(0 && "iconv_open失败!");//TODO 会递归调用自己,不能使用debug,改为不使用编码转换的debug
		return {};
	}
	
	//资源释放
	finally f([&]() {
		iconv_close(cd);
	});

	char temp[SIZE_BUFFER_STRING_CONVERT];//缓冲区
	string ret;//输出

	char* in_buf = (char*)buf;	//输入指针
	size_t in_size = buf_size;	//输入大小,直到0停止转换
	char* out_buf = temp;		//输出缓冲区指针
	size_t out_size = SIZE_BUFFER_STRING_CONVERT;//输出缓冲区大小
	
	do
	{//直到转换完
		if (-1 == iconv(cd, &in_buf, &in_size, &out_buf, &out_size))
		{
			if (errno != E2BIG)
			{
				assert(0 && "转换编码失败!");
				return {};
			}
			else
			{
				size_t num = SIZE_BUFFER_STRING_CONVERT - out_size;
				ret += string(temp, num);
				//没有转换完,重设输出位置
				out_size = SIZE_BUFFER_STRING_CONVERT;
				out_buf = temp;
			}
		}
		else
		{//完整转换,增加num
			assert(in_size == 0);
			size_t num = SIZE_BUFFER_STRING_CONVERT - out_size;
			if (ret.empty())//第一次就结束了,优化直接return
				return string(temp, num);
			ret += string(temp, num);
		}
	} while (in_size);

	return ret;
}

四、使用实例

        要实现任意多字节本地编码转为utf8编码,以下调用即可:

string String::cvt_mb_u8(const char* str, const string& code_name)
{
	return CvtString(code_name.c_str(), "UTF-8", str, strlen(str));
}

        code_name为空表示本地系统编码,也可以填入具体的编码,例如"GBK"。可以参考官网给出的全部支持的编码名字:

ICONV_OPEN

        如果涉及到wstring宽字符转换,则需要转换一下,因为wchar_t在不同平台大小不一样,所以使用sizeof:

std::wstring String::cvt_mb_wc(const char* str, const string& code_name)
{
	string out = CvtString(code_name.c_str(), "wchar_t", str, strlen(str));

	assert(out.size() % sizeof(wchar_t) == 0);//必须是sizeof(wchar_t)的倍数
	std::wstring ret((wchar_t*)out.c_str(), out.size() / sizeof(wchar_t));
	return ret;
}

        同理基于4字节的char32_t如此:

std::u32string String::cvt_u8_utf(const string& str)
{
	string out = CvtString("UTF-8", "UCS-4-INTERNAL", str.c_str(), str.size());

	assert(out.size() % sizeof(char32_t) == 0);//必须是sizeof(char32_t)的倍数
	std::u32string ret((char32_t*)out.c_str(), out.size() / sizeof(char32_t));
	return ret;
}

//反过来则这样转换
string String::cvt_utf_u8(const utf_string& str)
{
	return CvtString("UCS-4-INTERNAL", "UTF-8", (char*)str.c_str(), str.size() * sizeof(wchar_t));
}

        完整代码可以参考我的项目的DND.String.ixx文件:

DND3D: 基于C++20与标准库的工具集

        对你有帮助请点个赞、收藏或关注。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值