C++ 存在多种方式实现 GBK 到 UTF-8 的转码
1 - 使用 Qt API
一般使用C++都会想到使用 Qt API QString 将 gbk 转为 utf-8
std::string sgbk;
std::string sutf8 = QString::fromLocal8Bit(sgbk.data()).toUtf8().data());
此种方式,可以转换 Windows 平台运行时的 gbk 编码的中文字符串为 utf-8 格式,linux 下需要使用 QTextCodec ,网上有很多,此处不做过多描述。
由于项目需要去掉 Qt 依赖,或者无法使用 Qt,所有给出以下两种方法。
2 - 使用 std::codecvt
C++标准库封装了部分转码方法,需要通过 unicode 中转,调用 \ 与 \ 来实现。
首先实现四个基础方法
class String {
private:
static std::string UnicodeToUtf8(const std::wstring& wstr);
static std::wstring Utf8ToUnicode(const std::string& str);
static std::string UnicodeToAnsi(const std::wstring& wstr);
static std::wstring AnsiToUnicode(const std::string& str);
};
unicode 与 utf-8 之间的相互转换
std::string String::UnicodeToUtf8(const std::wstring& wstr)
{
std::string out;
try {
std::wstring_convert<std::codecvt_utf8<wchar_t>> wcv;
out = wcv.to_bytes(wstr);
}
catch (const std::exception & e)
{
std::cerr << e.what() << std::endl;
}
return out;
}
std::wstring String::Utf8ToUnicode(const std::string& str)
{
std::wstring ret;
try
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> wcv;
ret = wcv.from_bytes(str);
}
catch (const std::exception & e)
{
std::cerr << e.what() << std::endl;
}
return ret;
}
然后实现 unicode 与 ansi 之间的转换
std::string String::UnicodeToAnsi(const std::wstring& wstr)
{
std::string ret;
std::mbstate_t state{
};
const wchar_t* src = wstr.data();
size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
if (len != static_cast<size_t>(-1))
{
std::unique_ptr<char[]> buff(new char[len + 1]);
len = std::wcsrtombs(buff.get(), &src, len, &state);
if (len != static_cast<size_t>(-1))
{
ret.assign(buff.get(), len);
}
}
return ret;
}
std::wstring String::AnsiToUnicode(const std::string& str)
{
std::wstring ret;
std::mbstate_t state{
};
const char* src = str.data();
size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
if (len != static_cast<size_t>(-1))
{
std::unique_ptr<wchar_t[]> buff(new wchar_t[len + 1]);
len = std::mbsrtowcs(buff.get(), &src, len, &state);
if (len != static_cast<size_t>(-1))
{
ret.assign(buff.get(), len);
}
}
return ret;
}
然后实现最外层的方法
class String {
public:
static std::string Utf8ToAnsi(const std::string& str);
static std::string AnsiToUtf8(const std::string& str);
};
只需要级联调用即可
std::string String::Utf8ToAnsi(const std::string& str)
{
return UnicodeToAnsi(Utf8ToUnicode(str));
}
std::string String::AnsiToUtf8(const std::string& str)
{
return UnicodeToUtf8(AnsiToUnicode(str));
}
主函数入口处需设置 setlocale,此处调用为了使 ANSI 编码生效,由于 ANSI 在不同平台下表示的编码不同。
// to let ANSI take effects to enable AnsiToUtf8
setlocale(LC_CTYPE, "");
完整代码 String.h,实现一个纯接口类
#pragma once
#include <string>
class String
{
public:
// 对外接口
static std::string Utf8ToAnsi(const std::string& str);
static std::string AnsiToUtf8(const std::string& str);
private:
// 内部调用
static std::string UnicodeToUtf8(const std::wstring& wstr);
static std::wstring Utf8ToUnicode(const std::string& str);
static std::string UnicodeToAnsi(const std::wstring& wstr);
static std::wstring AnsiToUnicode(const std::string& str);
// disabled functions
String() = delete;
~String() = delete;
String(const String& rhs) = delete;
String& operator=(const String& rhs) = delete;
};
#pragma once
现在较新版本的编译器一般都支持,如果不支持需要换成#ifndef __STRING_H__ #define __STRING_H__ #endif // __STRING_H__
防止头文件重复包含
String.cpp
#include "String.h"
#include <codecvt>
#include <iostream>
std::string String::Utf8ToAnsi(const std::string& str)
{
return UnicodeToAnsi(Utf8ToUnicode(str));
}
std::string String::AnsiToUtf8(const std::string& str)
{
return UnicodeToUtf8(AnsiToUnicode(str));
}
std::string String::UnicodeToUtf8(const std::wstring& wstr)
{
std::string out;
try {
std::wstring_convert<std::codecvt_utf8<wchar_t>> wcv;
out = wcv.to_bytes(wstr);
}
catch (const std::exception & e)
{
std::cerr << e.what() << std::endl;
}
return out;
}
std::wstring String::Utf8ToUnicode(const std::string& str)
{
std::wstring ret;
try
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> wcv;
ret = wcv.from_bytes(str);
}
catch (const std::exception & e)
{
std::cerr << e.what() << std::endl;
}
return ret;
}
std::string String::UnicodeToAnsi(const std::wstring& wstr)
{
std::string ret;
std::mbstate_t state{
};
const wchar_t* src = wstr.data();
size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
if (len != static_cast<size_t>(-1))
{
std::unique_ptr<char[]> buff(new char[len + 1]);
len = std::wcsrtombs(buff.get(), &src, len, &state);
if (len != static_cast<size_t>(-1))
{
ret.assign(buff.get(), len);
}
}
return ret;
}
std::wstring String::AnsiToUnicode(const std::string& str)
{
std::wstring ret;
std::mbstate_t state{
};
const char* src = str.data();
size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
if (len != static_cast<size_t>(-1))
{
std::unique_ptr<wchar_t[]> buff(new wchar_t[len + 1]);
len = std::mbsrtowcs(buff.get(), &src, len, &state);
if (len != static_cast<size_t>(-1))
{
ret.assign(buff.get(), len);
}
}
return ret;
}
由于 setlocale 会影响全局,也就是所有的 lib 库都会影响,有可能会出现问题,所以建议第三种方法。
3 - 使用 WinAPI 和 iconv
Windows 平台使用 Win API, linux 平台使用 iconv 库
首先实现一个 linux 下的通用函数,由于直接使用 std::string 和 iconv 接口会出现转换失败的问题。
// 根据不同的平台包含不同的头文件
#if defined(_WIN32) || defined(_MSC_VER) || defined(WIN64)
#include <Windows.h>
#elif defined(__linux__) || defined(__GNUC__)
#include <iconv.h>
#endif
#if defined(__linux__) || defined(__GNUC__)
int EncodingConvert(const char* charsetSrc, const char* charsetDest, char* inbuf,
size_t inSz, char* outbuf, size_t outSz)
{
iconv_t cd;
char** pin = &inbuf;
char** pout = &outbuf;
cd = iconv_open(charsetDest, charsetSrc);
if (0 == cd)
{
std::cerr << charsetSrc << " to " << charsetDest
<< " conversion not available" << std::endl;
return -1;
}
if (-1 == static_cast<int>(iconv(cd, pin, &inSz, pout, &outSz)))
{
std::cerr << "conversion failure" << std::endl;
return -1;
}
iconv_close(cd);
**pout = '\0';
return 0;
}
#endif
实现 GBK 转 UTF-8 的接口,设置转换失败和非 Windows 和非 Linux 系统,返回原字符串。
std::string GbkToUtf8(const std::string& str)
{
#if defined(_WIN32) || defined(_MSC_VER) || defined(WIN64)
int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1ull];
memset(wstr, 0, len + 1ull);
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* cstr = new char[len + 1ull];
memset(cstr, 0, len + 1ull);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, cstr, len, NULL, NULL);
std::string res(cstr);
if (wstr) delete[] wstr;
if (cstr) delete[] cstr;
return res;
#elif defined(__linux__) || defined(__GNUC__)
size_t len = str.size() * 2 + 1;
char* temp = new char[len];
if (EncodingConvert("gb2312", "utf-8", const_cast<char*>(str.c_str()), str.size(), temp, len)
> = 0)
{
std::string res;
res.append(temp);
delete[] temp;
return res;
}
else
{
delete[]temp;
return str;
}
#else
std::cerr << "Unhandled operating system." << std::endl;
return str;
#endif
}
实现 UTF-8 转 GBK 的接口,与前者一样,非 Windows 和非 Linux 系统未处理和处理失败返回原字符串
std::string Utf8ToGbk(const std::string& str)
{
#if defined(_WIN32) || defined(_MSC_VER) || defined(WIN64)
// calculate length
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
wchar_t* wsGbk = new wchar_t[len + 1ull];
// set to '\0'
memset(wsGbk, 0, len + 1ull);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, wsGbk, len);
len = WideCharToMultiByte(CP_ACP, 0, wsGbk, -1, NULL, 0, NULL, NULL);
char* csGbk = new char[len + 1ull];
memset(csGbk, 0, len + 1ull);
WideCharToMultiByte(CP_ACP, 0, wsGbk, -1, csGbk, len, NULL, NULL);
std::string res(csGbk);
if (wsGbk)
{
delete[] wsGbk;
}
if (csGbk)
{
delete[] csGbk;
}
return res;
#elif defined(__linux__) || defined(__GNUC__)
size_t len = str.size() * 2 + 1;
char* temp = new char[len];
if (EncodingConvert("utf-8", "gb2312", const_cast<char*>(str.c_str()),
str.size(), temp, len) >= 0)
{
std::string res;
res.append(temp);
delete[] temp;
return res;
}
else
{
delete[] temp;
return str;
}
#else
std::cerr << "Unhandled operating system." << std::endl;
return str;
#endif
}
GBK 转 UTF-8 两个平台均验证测试可行。