002-libIconv 字符集转换库
一、基础知识
UNICODE——统一码
1.libIconv转换标准

2.libIconv典型用法

- 打开转换句柄
- 使用句柄循环转换
- 关闭句柄
3.转换过程详解
size_t iconv(iconv_t cd,
char** inbuf, size_t* inbytesleft,
char** outbuf, size_t* outbytesleft
);
参数解析:
cd:句柄;inbuf:所要转换的字符串的缓冲区,虽然没有用const修饰符修饰,但在转换过程中,字符串内容不会被修改;inbytesleft:用于指示源字符串inbuf有多少个字节;outbuf:转换后字符串缓冲区;outbytesleft:用于指示存放结果的outbuf有多少字节。
为什么缓冲区要使用
char**类型?虽然源字符串不会被修改,但是指向源字符串的指针会修改。
举例来说:输入缓冲区有“我爱你”的国标字符(国标每字符占用2字节),在转换过程中,可能转换到第4个字节就停下了。这个时候,作为一个指向源字符串缓冲区的指针,
inbuf就会停在[4]的位置,即:只成功转换“我爱”两个字符。对应的,随着转换的进行,outbuf也会向后偏移,直到转换完成或者转换出现问题停下来。同样的,inbytesleft作为size_t*类型的指针,可以用来告知上层调用者,还有多少输入字符待转换;outbytesleft告知调用者,输出缓冲区还有多少空间可以用来填充转换后的数据。
3.1iconv()返回-1的情况,即:转换什么时候会停下来?

二、代码实践
1.设计思路
考虑转换失败的原因:
- 源码格式和目标格式错误;
- 输入字符串不完整或者大小指示错误;
- 输出字符串缓冲区溢出。
通常认为,源码格式和目标格式均为固定值,而且输入字符串大小和内容均为已知,那么为了避免错误产生,就应当在 outbuf上下功夫:
- 方法1:申请一个巨大的
outbuf确保其永远够用- 浪费内存;
- 通常需要动态分配内存,效率低。
- 方法2:包装函数
- 要求用户传入的源字符串的内容必须是完整的;
- 使用固定大小的
outbuf作为输出字符串缓冲区,循环调用,将转换的结果填充到真正的目标缓冲区。
2.代码实现
#include <iconv.h>
#include <iostream>
#include <sstream>
#include <string>
// 转换结构
struct IConvResult {
std::string result; // 转换成功得到的,使用新编码的字符串
int errorNum = 0; // 转换过程中发生的错误码,errno
std::string errorMsg; // 转换过程中发生的错误信息
};
IConvResult iconvConvert(std::string_view in, char const* fromCode, char const* toCode)
{
IConvResult ir;
// 调用iconv_open函数打开一个转换句柄
iconv_t cd = iconv_open(toCode, fromCode);
if (cd == (iconv_t)(-1)) {
ir.errorNum = errno;
switch (ir.errorNum) {
case EILSEQ:
ir.errorMsg = "非法字符序列";
break;
case EINVAL:
ir.errorMsg = "非法参数";
break;
case ENOMEM:
ir.errorMsg = "内存不足";
break;
default:
ir.errorMsg = "未知错误";
break;
}
return ir;
}
char* inBufferPtr = const_cast<char*>(in.data()); // 指向输入缓存数据
size_t inBytesLeft = in.size(); // 输入缓存大小
std::stringstream ss;
std::size_t const sizeOfOutBuffer = 20;
char outBuffer[sizeOfOutBuffer];
while (inBytesLeft > 0) {
char* outBufferPtr = outBuffer;
size_t outBytesLeft = sizeOfOutBuffer;
// 调用iconv函数进行转换
size_t result = iconv(cd, &inBufferPtr, &inBytesLeft, &outBufferPtr, &outBytesLeft);
if (result == (size_t)(-1)) // 转换停止了
{
auto n = errno; // 获取错误码
switch (n) {
case E2BIG:
ir.errorMsg = "输出缓冲区太小";
break;
case EILSEQ:
ir.errorNum = n;
ir.errorMsg = "非法字符序列";
break;
case EINVAL:
ir.errorNum = n;
ir.errorMsg = "非法参数";
break;
default:
ir.errorMsg = "未知错误";
break;
}
}
if (ir.errorNum != 0) {
break;
}
ss.write(outBuffer, sizeOfOutBuffer - outBytesLeft);
}
iconv_close(cd);
if (ir.errorNum == 0) {
ir.result = ss.str();
}
return ir;
}
int main(int, char**)
{
std::system("chcp 65001 > nul");
extern char const* gbk_str;
std::cout << "original string: " << gbk_str << std::endl;
auto ir = iconvConvert(gbk_str, "gbk", "utf-8");
if (ir.errorNum == 0) {
std::cout << "converted string: " << ir.result << std::endl;
} else {
std::cout << "error: " << ir.errorMsg << std::endl;
std::cout << "converted string: " << ir.result << std::endl;
}
std::system("pause");
}