C/C++语言中的字符类型

存在两种表示字符的基本类型:

char:一个字节8bit表示,最多表示256个字符,表示和用来处理ASCII字符集,国际通用

wchar_t:多字节字符表示,典型2个字节或者4个字节,如GNU libc中为4B,可以表示更多的字符,满足国际化应用开发的需求,实现标准 在开发中ASCII编码字符都是用char来表示,可以转换成wchar_t表示;wchar_t类型与Unicode编码是完全独立的概念,不过在实现上Unicode编码一般用wchar_t来表示实现而已,但wchar_t字符并不一定就是Unicode编码字符。

对应两种字符类型存在两种字符串类型(C++):

string: char字符列表或者是字节列表(bytes)

wstring: wchar_t字符列表或者是宽子节列表

对应两种字符类型的输出函数流对象有:

sprintf/wsprintf: 分别对应char与wchar_t

cout/wcout:分别对应string与wstring

stringstream/wstringstream: 分别对应string与wstring

字符串常量

C++98标准中一些表示字符串常量的标识有:

“您好”: string字符串常量(字节数组),使用当前系统代码页进行编码

C++11标准中增加了一些表示字符串常量的标识,如下有:

L“您好!”: wstring字符串常量,使用文件保存编码方式字符集

R“(您好 \n)”: 原始字符串常量(字节数组),保留所有的字符

u8“您好!”: string字符串常量(字节数组),使用UTF8进行编码保存

字符集与字符编码的关系

一般一个字符集等同于一个编码方式,ANSI体系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。

一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。

已知有很多的字符集,比如:ASCII,UTF8,GBK,GB2312,UTF16,UTF32,UNICODE,Latin等等。常用中文字符集编码有:

UTF8:又分为带签名和不带签名两种,Windows代码页为65001,VS中应该选择【UTF8-带签名】的格式

GBK/GB2312:Windows代码页为936

GB18030: Windows代码页为54936

小技巧:修改Windows系统中cmd命令行窗口的显示字符集,默认字符集为OS字符集,如GBK-936。如果希望显示UTF8字符,则可以修改:chcp 65001.

在VS项目的调试命令窗口中无法手动修改,可以通过system函数来修改:

system("C:\\Windows\\system32\\chcp 65001");// 修改终端字符集为UTF8

源文件的编码保存选项

可以将源文件保存成不同的编码方式,如果文件中有中文,则必须选择可以对中文进行编码的字符集,如UTF8,GBK,GB2312等,否认可能会出现莫名其妙的编译错误,因为文件中存在无法识别的字符内容。

在Visual Studio中,选中文件后该设置在:【文件 - 高级保存选项】中。

C++11中GBK/UTF/wchar_t之间的编码处理转换

在处理中文时,不同的应用场景下总是无法避免进行GBK和UTF8之间的相互转换,C++11标识提供了<locale>和<codecvt>机制和工具来简化处理。

借助其中的std::wstring_convert和std::codecvt_utf8模板,通过wchar_t类型为中介,可以快速地实现转换,基本代码如下:

/*
    转换GBK编码的中文到UTF8编码:GBK - WChar - UTF8两次转换
*/
//GBK在linux下的locale名可能是"zh_CN.GBK" 
const char* GBK_LOCALE_NAME = ".936"; //GBK在windows下的locale name 
std::wstring_convert<std::codecvt_byname<wchar_t, char, mbstate_t>> Conver_GBK(new codecvt_byname<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME)); //GBK - wchar_t
std::wstring _wname = Conver_GBK.from_bytes(data.Name); // 输入为char*的字符串表示或者数组,输出为wstring
std::wstring _waddr = Conver_GBK.from_bytes(data.Address);// 输入为char*的字符串表示或者数组,输出为wstring
std::wstring _wdept = Conver_GBK.from_bytes(data.GrantDept);// 输入为char*的字符串表示或者数组,输出为wstring

wcout << "Name: " << _wname << ",addr:" << _waddr << ",dept:" << _wdept << endl;

// 将wstring转化为UTF8编码的字节数组string表示
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
string _name = conv.to_bytes(_wname);// 输入为wstring字符串
string _addr = conv.to_bytes(_waddr);
string _dept = conv.to_bytes(_wdept);

// 将UTF8字符串转换为GBK字符编码表示string字节数组
std::string _name = Conver_GBK.to_bytes(conv.from_bytes(_name)); // 先转换成wstring,然后再转换成GBK的string
std::string _addr = Conver_GBK.to_bytes(conv.from_bytes(_addr)); // 先转换成wstring,然后再转换成GBK的string

活动代码页

代码页是字符集编码的别名,也有人称“内码表”。下面列出了部分代码页及其国家(地区)或者语言:

代码页 国家(地区)或语言

437 美国

932 日文(Shift-JIS)

936 中国 - 简体中文(GB2312)

950 繁体中文(Big5)

1200 Unicode

1201 Unicode (Big-Endian)

50220 日文(JIS)

50225 韩文(ISO)

50932 日文(自动选择)

50949 韩文(自动选择)

52936 简体中文(HZ)

65000 Unicode (UTF-7)

65001 Unicode (UTF-8)

C++11字符串换初始化方式

char16_t* p1 = u"中国";//把字符串初始化为UTF16字符串存储
char32_t* p2 = U"中国";//把字符串初始化为UTF32字符串存储
wchar_t* p3 = L"中国";//win是UCS2码下等同UTF16字符串,Linxu是UCS4码下等同utf32字符串
char* p4 = u8"中国";//把字符串初始化为UTF8字符串存储
char* p5 = "中国";//p5字符的编码格式见我的上篇博文《c++字符串乱码研究》
std::string str1 = u8"中国";
std::wstring str2 = L"中国";
std::u16String str3 = u"中国";
std::u32String str4 = U"中国";

字符串转换

//std::string 转为 std::wstring( utf-8 --> wchar )
//src必须是UTF8否则抛异常
std::wstring utf8_to_wstr(const std::string& src)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    return converter.from_bytes(src);
}
//std::wstring转为std::string(wchar --> utf-8)
std::string wstr_to_utf8(const std::wstring& src)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
    return convert.to_bytes(src);
}

在构造std::wstring_convert对象的时候需要传入一个codecvt对象的指针,如果没有传入,则默认使用new codecvt来创建。std::wstring_convert自行维护codecvt对象的生命周期,它的析构函数会调用delete操作符来删除该对象。这就限制了只能使用通过new操作符来创建的codecvt,而不能使用从std::locale中获取的codecvt。

在C++标准提供的codecvt中,能够直接用于std::wstring_convert的只有三个:std::codecvt_utf8,std::codecvt_utf16以及std::codecvt_utf8_utf16。可见,标准只支持UTF族的字符编码。为了获取其它字符编码的codecvt,需要使std::codecvt_byname,这个类可以通过字符编码的名称来创建一个codecvt。这看起来挺不错,但遗憾的是,字符编码的名称并没有统一的标准,各个平台的支持情况都不一样。例如,在Windows下可以使用“chs”或“.936”来创建简体中文编码的codecvt,linux下可是“zh_CN.GBK”在Mac OS X下则要使用“zh_cn.gb2312”;甚至在Mac OS X下,即使成功创建了这个codecvt,它也不能正常地转换。

由于历史原因,std::codecvt_byname的析构函数是protected的(g++是,VC好像不是),std::wstring_convert不能对它调用delete,所以首先要自行定义一个类来继承std::codecvt_byname:

class chs_codecvt : public std::codecvt_byname<wchar_t, char, std::mbstate_t>
{
public:
    chs_codecvt() : codecvt_byname("chs") {} //zh_CN.GBK or .936
};
// std::wstring 转 GBK(wchar --> ansi) 
std::string wstr_to_gbk(const std::wstring& str)
{
    std::wstring_convert<chs_codecvt> converter;
    return converter.to_bytes(str);
}
//GBK 转为 std::wstring( ansi --> wchar ) 
std::wstring gbk_to_wstr(const std::string& str)
{
    std::wstring_convert<chs_codecvt> converter;
    return converter.from_bytes(str);
}
#include<string>
#include<windows.h>
#include<vector>
using namespace std;
 
//utf8 转 Unicode
std::wstring Utf82Unicode(const std::string& utf8string)
{
    int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);
    if (widesize == ERROR_NO_UNICODE_TRANSLATION)
    {
        throw std::exception("Invalid UTF-8 sequence.");
    }
    if (widesize == 0)
    {
        throw std::exception("Error in conversion.");
    }
 
    std::vector<wchar_t> resultstring(widesize);
 
    int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);
 
    if (convresult != widesize)
    {
        throw std::exception("La falla!");
    }
 
    return std::wstring(&resultstring[0]);
}

//unicode 转为 ascii
std::string WideByte2Acsi(std::wstring& wstrcode)
{
    int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);
    if (asciisize == ERROR_NO_UNICODE_TRANSLATION)
    {
        throw std::exception("Invalid UTF-8 sequence.");
    }
    if (asciisize == 0)
    {
        throw std::exception("Error in conversion.");
    }
    std::vector<char> resultstring(asciisize);
    int convresult =::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);
 
    if (convresult != asciisize)
    {
        throw std::exception("La falla!");
    }
 
    return std::string(&resultstring[0]);
}

//utf-8 转 ascii
std::string UTF_82ASCII(std::string& strUtf8Code)
{
    std::string strRet("");
    //先把 utf8 转为 unicode
    std::wstring wstr = Utf82Unicode(strUtf8Code);
    //最后把 unicode 转为 ascii
    strRet = WideByte2Acsi(wstr);
    return strRet;
}

//ascii 转 Unicode
std::wstring Acsi2WideByte(std::string& strascii)
{
    int widesize = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);
    if (widesize == ERROR_NO_UNICODE_TRANSLATION)
    {
        throw std::exception("Invalid UTF-8 sequence.");
    }
    if (widesize == 0)
    {
        throw std::exception("Error in conversion.");
    }
    std::vector<wchar_t> resultstring(widesize);
    int convresult = MultiByteToWideChar (CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);
 
 
    if (convresult != widesize)
    {
        throw std::exception("La falla!");
    }
 
    return std::wstring(&resultstring[0]);
}

//Unicode 转 Utf8
std::string Unicode2Utf8(const std::wstring& widestring)
{
    int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);
    if (utf8size == 0)
    {
        throw std::exception("Error in conversion.");
    }
 
    std::vector<char> resultstring(utf8size);
 
    int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);
 
    if (convresult != utf8size)
    {
        throw std::exception("La falla!");
    }
 
    return std::string(&resultstring[0]);
}
 
//ascii 转 Utf8
std::string ASCII2UTF_8(std::string& strAsciiCode)
{
    std::string strRet("");
    //先把 ascii 转为 unicode
    std::wstring wstr = Acsi2WideByte(strAsciiCode);
    //最后把 unicode 转为 utf8
    strRet = Unicode2Utf8(wstr);
    return strRet;
}
std::string StringToUTF8(const std::string& gbkData)
{
    const char* GBK_LOCALE_NAME = "CHS";  //GBK在windows下的locale name(.936, CHS ), linux下的locale名可能是"zh_CN.GBK"

    std::wstring_convert<std::codecvt<wchar_t, char, mbstate_t>>
        conv(new std::codecvt<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
    std::wstring wString = conv.from_bytes(gbkData);    // string => wstring

    std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
    std::string utf8str = convert.to_bytes(wString);     // wstring => utf-8

    return utf8str;
}

std::string UTF8ToString(const std::string& utf8Data)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
    std::wstring wString = conv.from_bytes(utf8Data);    // utf-8 => wstring

    std::wstring_convert<std::codecvt< wchar_t, char, std::mbstate_t>>
        convert(new std::codecvt< wchar_t, char, std::mbstate_t>("CHS"));
    std::string str = convert.to_bytes(wString);     // wstring => string

    return str;
}

https://www.cnblogs.com/bigben0123/p/13728595.html

除额外注明的地方外,本维基上的内容按下列许可协议发布: CC Attribution-Share Alike 4.0 International