以下是我个人关于C和C++中字符编码的总结,有其它意见欢迎指出和补充。
目录
1、字符集
字符集是一组字符的集合,其中每个字符都有一个唯一的编码。由于不同国家和地区使用的符号不一样,所以存在多种字符集。常见字符集如下:
GB2312字符集:定义了6763个汉字和682个非汉字字符,这些字符主要用于简体中文,包括拉丁字母、希腊字母、日文平假名和片假名等。
GBK字符集:在GB2312字符集的基础上进行了扩展,包含更多的汉字和符号,总字符数超过2万个,包括简体中文、繁体中文和部分日文汉字。
ASCII(American Standard Code for Information Interchange):一个7位字符集,是最早的字符编码标准之一,用一个字节(8位)来表示一个字符,包含128个字符,包括控制字符(如换行、回车)和可打印字符(如字母、数字、标点符号)。ASCII 编码在计算机系统中广泛应用,尤其是在早期的系统和通讯协议中。
ISO-8859:一系列8位字符集,扩展了ASCII字符集,支持多种西方语言的字符。
Unicode:一个包含了几乎所有书写系统字符的字符集,旨在统一各国各地区的字符编码。编码范围为0x0000 - 0x10FFFF ,可以容纳一百多万个字符,其中每个字符的二进制数值也叫码点。
2、字符编码
字符编码是将字符集中的字符转换为计算机可处理的二进制形式的规则。有的字符集只有一种编码方式,如ASCII、GBK等,有的字符集有多种编码方式,如Unicode。Unicode存在多种编码方式的原因如下:
Unicode 字符集的编码范围为 0x0000 - 0x10FFFF ,因此一个字符需要 1 到 3 个字节来表示,这就有两个问题:一是对于三个字节的 Unicode字符,计算机怎么知道它表示的是一个字符、两个字符还是三个字符;二是如果所有字符都用三个字节表示,那么对于那些一个字节就能表示的字符来说,有两个字节是无意义的,对于存储来说,这是极大的浪费。因此,Unicode 出现了多种存储方式,即 UTF-8、UTF-16、UTF-32。
常见的字符编码方式有:
ASCII编码:使用7位或8位来表示一个字符。由于它只使用一个字节来表示字符,所以它既是字符集,又是字符编码。
GB2312编码:既是字符集,又是字符编码方式。采用双字节编码,每个字符占用两个字节。其编码范围是:第一字节在0xA1-0xF7之间,第二字节在0xA1-0xFE之间。
GBK编码:既是字符集,又是字符编码方式。也是双字节编码,但扩展了编码范围。其编码范围是:第一字节在0x81-0xFE之间,第二字节在0x40-0xFE之间(除去0x7F)。
UTF-8(Unicode Transformation Format - 8-bit,Unicode 转换格式):一种变长字符编码方式,使用1到4个字节来编码Unicode字符,是互联网和现代应用程序中最常见的字符编码方式之一。它通常使用三个或四个字节来表示中文字符。它向后兼容ASCII,非常适合网络传输和文件存储。
UTF-16:使用16位(2字节)来编码Unicode字符,对于BMP(基本多文种平面)字符使用单个16位编码,对于超出BMP范围的字符使用代理对(两个16位编码)来表示。
UTF-32:使用固定的32位(4字节)来编码每个Unicode字符,能够直接表示所有Unicode字符,不需要代理对。
ANSI :指代在 Windows 系统上的本地代码页(Code Page)。它并不是某一种特定的字符编码,而是在不同的国家和地区,有不同的取值,比如美国的ANSI编码其实是ASCII编码,中国的ANSI编码其实是GBK编码,韩文系统中ANSI编码其实是EUC-KR编码。微软用一个叫“Windows code pages”(本地代码页)的东西来收录编码格式,比如简体中文的本地代码页值为936(GBK编码),繁体中文的code page值为950(表示Big-5编码)。在命令行页面,我们可以通过chcp命令来查看当前系统的本地代码页的值,也就是ANSI的具体取值。
3、UTF-8编码规则
UTF-8 的编码规则很简单,只有二条:
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
4、宽字符
在C语言中,宽字符(wide character)是指使用多字节来表示的字符类型,用wchar_t表示。使用 wchar_t 可以方便地处理国际化字符集,如中文、日文、韩文等。宽字符在不同系统上有不同的实现,例如:
Windows上:wchar_t通常是16位(2字节),使用UTF-16编码。
UNIX/Linux上:wchar_t通常是32位(4字节),使用UTF-32编码。
wchar_t的使用和char略有不同,需要使用字符串前缀 L 表示这是一个宽字符字符串。实例如下:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
int main() {
// 设置locale为简体中文,GB2312编码
setlocale(LC_ALL, "zh_CN.GB2312");
// 使用宽字符数组存储GB2312字符串
wchar_t gb2312_str[] = L"你好";
wprintf(L"GB2312 String: %ls\n", gb2312_str); //专门用于宽字符输出的函数
// 设置locale为简体中文,GBK编码
setlocale(LC_ALL, "zh_CN.GBK");
// 使用宽字符数组存储GBK字符串
wchar_t gbk_str[] = L"你好,世界!";
wprintf(L"GBK String: %ls\n", gbk_str);
return 0;
}
5、转换编码格式
在实际处理字符串时,有时会需要转换字符串的编码格式,这是因为:
- 在数据传输和存储过程中,不同的协议和文件格式可能对字符编码有不同的要求。
- 一些三方库可能只支持特定的编码格式,而不会自动跟ANSI对齐。一般来说,现在大多数库都使用UTF-8编码,而我们中国的ANSI是GBK,所以在涉及字符串参数时,一般都需要转换一下。比如avformat_open_input 就需要utf-8编码格式的字符串。
Windows上编程时,可以通过连续使用两个Windows API来转换编码:MultiByteToWideChar和WideCharToMultiByte。前者将指定编码的多字节字符串转为宽字符串,后者将宽字符串转为指定编码的多字节字符串。整个过程类似为一个转换器,将多种编码转换为一种编码。这两个函数的用法懒得写了,直接给个例子吧:
std::string AudioProcessorUnit::StringToUTF8String(const char* data)
{
int len = strlen(data);
if (len <= 0) {
return data;
}
int resultLen = MultiByteToWideChar(CP_ACP, 0, data, len, nullptr, 0);
std::wstring wstr(resultLen, L'\0');
MultiByteToWideChar(CP_ACP, 0, data, len, &wstr[0], resultLen);
len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), nullptr, 0, nullptr, nullptr);
char* buffer = new char[len + 1];
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.size(), buffer, len, nullptr, nullptr);
buffer[len] = '\0';
string result(buffer);
delete[] buffer;
return result;
}