IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【字符编码转换】使用iconv -> 正文阅读

[C++知识库]【字符编码转换】使用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与标准库的工具集

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-28 11:37:29  更:2022-04-28 11:38:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 0:17:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码