一、配置
配置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"。可以参考官网给出的全部支持的编码名字:
如果涉及到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文件:
对你有帮助请点个赞、收藏或关注。